我正在使用Go语言编写的Twitch.tv网站聊天机器人。
该机器人的功能之一是积分系统,奖励观看特定视频流的用户。这些数据存储在SQLite3数据库中。
为吸引观众,该机器人进行了一个API调用来抽搐并收集流中的所有当前观众。然后将这些查看者放在一串字符串中。
总观看者的范围从几对到20,000或更多。
机器人的功能
代码
type Viewers struct {
Chatters struct {
CurrentModerators []string `json:"moderators"`
CurrentViewers []string `json:"viewers"`
} `json:"chatters"`
}
func RunPoints(timer time.Duration, modifier int, conn net.Conn, channel string) {
database := InitializeDB() // Loads database through SQLite3 driver
var Points int
var allUsers []string
for range time.NewTicker(timer * time.Second).C {
currentUsers := GetViewers(conn, channel)
tx, err := database.Begin()
if err != nil {
fmt.Println("Error starting points transaction: ", err)
}
allUsers = append(allUsers, currentUsers.Chatters.CurrentViewers...)
allUsers = append(allUsers, currentUsers.Chatters.CurrentModerators...)
for _, v := range allUsers {
userCheck := UserInDB(database, v)
if userCheck == false {
statement, _ := tx.Prepare("INSERT INTO points (Username, Points) VALUES (?, ?)")
statement.Exec(v, 1)
} else {
err = tx.QueryRow("Select Points FROM points WHERE Username = ?", v).Scan(&Points)
if err != nil {
} else {
Points = Points + modifier
statement, _ := tx.Prepare("UPDATE points SET Points = ? WHERE username = ?")
statement.Exec(Points, v)
}
}
}
tx.Commit()
allUsers = allUsers[:0]
currentUsers = Viewers{} // Clear Viewer object struct
}
预期行为
当吸引成千上万的观众时,我自然希望系统资源会变得很高。这可以使漫游器使用3.0 MB的RAM,最高可达20 MB +。当然,成千上万的元素会占用大量空间!
但是,还有其他事情发生。
实际行为
每次调用API时,RAM都会按预期增加。但是,因为我清除了该切片,所以我希望它可以降到其“正常”的3.0 MB使用量。
但是,每个API调用的RAM使用量会增加,即使流的查看器总数增加,也不会减少。
因此,花了几个小时,该漫游器会轻易消耗100 + MB的ram,这对我来说似乎不正确。
我在这里想念什么?一般来说,我对编程和CS还是相当陌生,所以也许我正在尝试解决一些问题。但这听起来像是我的内存泄漏。
我尝试通过Golang的运行时库强制进行垃圾回收并释放内存,但这并不能解决问题。
答案 0 :(得分:2)
要了解此处发生的情况,您需要了解切片的内部及其发生的情况。您可能应该以https://blog.golang.org/go-slices-usage-and-internals
开始给出一个简短的答案:切片可以查看基础数组的一部分,而当您试图截断切片时,您要做的只是减少对数组的查看,但是底层阵列不受影响,并且仍占用相同的内存。实际上,通过继续使用同一阵列,您永远不会减少正在使用的内存量。
我鼓励您阅读此书的工作原理,但作为一个为什么没有实际内存释放的示例,请看一下这个简单程序的输出,该输出演示了如何对切片进行截断内部分配的内存:https://play.golang.org/p/PLEZba8uD-L
答案 1 :(得分:1)
切片时:
allUsers = allUsers[:0]
所有元素仍在后备数组中,无法收集。内存仍处于分配状态,这将在下次运行中节省一些时间(不必调整数组大小,节省缓慢的分配),这是将其切成零长度而不是仅仅转储它的关键。 / p>
如果要将内存释放给GC,则只需将其完全转储并每次都创建一个新片。这样会比较慢,但是在两次运行之间使用更少的内存。但是,这并不一定意味着您会看到该进程使用的内存更少。 GC会收集未使用的堆对象,然后最终可以将该内存释放给OS,如果其他进程施加了内存压力,则OS可能最终将其回收。