同时从两个通道消耗会导致 goroutines 占用我的 RAM



我对并发性很陌生,我决定在goroutines中运行http处理程序的几个部分,它开始占用我的RAM并冻结了我的计算机。

这就是我要做的:

我的处理程序具有这两个函数调用

qChan := cloneQuestions(currentFormView.QuestionObjects, currentForm.Id, currentForm.VersionNumber, now)
rChan := cloneRules(currentFormView.RuleObjects, currentForm.Id, currentForm.VersionNumber, now)

这两个函数调用都在一个名为 helpers.go 的文件中定义,它们将结构通道返回给调用方。

这是克隆问题的函数定义,克隆规则遵循与此相同的模式

func cloneQuestions(questions []question.Model, formID string, versionNumber int, now time.Time) <-chan question.Model {
out := make(chan question.Model)
go func() {
for _, currentQuestion := range questions {
out <- getClonedQuestion(currentQuestion, formID, versionNumber, now)
}
close(out)
}()
return out
}
func getClonedQuestion(currentQuestion question.Model, formID string, versionNumber int, now time.Time) question.Model {
newOptionsArray := cloneQuestionOptions(currentQuestion, formID, now)

return currentQuestion
}

cloneRules与此非常相似

在我的处理程序中调用这两个函数并将其返回的通道存储在 qChan 和 rChan 变量中(如上所述)后,我运行一个无限循环以同时消耗来自两个通道的值,并在停止从两个通道接收值后脱离循环,这是代码

for {
select {
case clonedQuestion := <-qChan:
insertQuestionToFormTxn := h.service.Mongo.GetAppendTxn(ctx, form.COLLECTION, currentForm.FormID, "questions", clonedQuestion.Id, clonedQuestion.Order)
newQuestionTxn := h.service.Mongo.GetNewTxn(ctx, question.COLLECTION, clonedQuestion, clonedQuestion.Id)
// collect all the txns in the arrray
array = append(array, insertQuestionToFormTxn, newQuestionTxn)
case clonedRule := <-rChan:
newRuleTxn := h.service.Mongo.GetNewTxn(ctx, rule.COLLECTION, clonedRule, clonedRule.Id)
// collect all the txns in the arrray
array = append(array, insertRuleToFormTxn, newRuleTxn)
default:
break
}
}

当我向这个处理程序发出请求并且我并排运行时,我看到它开始填满我的 RAM 并冻结我的机器,为什么会这样?

正如@JimB建议的那样,从 select 语句中删除default子句,因为它会导致一个紧密循环,从而绑定您的 CPU。

Select 的可选默认值允许对通道进行非阻塞轮询。在您的情况下,您希望等待任一渠道项目。在大多数情况下,阻止比轮询更好。

如果您担心服务可能会因阻塞而处于睡眠状态,您可以添加一个计时器通道来显示活动,例如

t := time.NewTicker(time.Minute)
for {
select {
case i := <-ch:
log.Println("got item:", i)
case <-t.C:
log.Println("I'm not dead yet!")
}
}

操场

很难说不分析你的程序,或者更好的是,跟踪它。如果启用跟踪,您将轻松看到代码分配大部分内存的位置。

但是从您的实现中可能会注意到两件事:

  1. 为什么要克隆对象?其他 goroutine 是否正在访问它们?否则,您可以直接使用这些对象,避免克隆成本。
  2. 您确定需要通道和 goroutines 来处理questionsrules对象吗?在我看来,在cloneQuestions函数中,您不执行任何I/O操作,实际上您只是克隆对象并将其发送到通道。最后,获取这些对象并将其全部追加到单个切片中(即array)。因此,该算法本质上看起来像是顺序的,您将不会从使用并发中获得任何性能优势。

最新更新