同时使用两个通道会导致goroutine占用我的RAM

时间:2019-06-08 10:02:00

标签: go concurrency

我对并发还很陌生,因此我决定在goroutines中运行http处理程序的一部分,它开始消耗我的RAM并冻结了我的计算机。

这就是我想要做的:

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

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

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

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

这是cloneQuestions的函数定义,clonedRules遵循与此模式相同的模式

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
    }
}

当我向此处理程序发出请求并并排运行htop时,我看到它开始填充我的RAM并冻结了我的机器,为什么会发生这种情况?

2 个答案:

答案 0 :(得分:1)

正如@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!")
    }
}

playground

答案 1 :(得分:0)

如果不对程序进行概要分析甚至更好地进行跟踪,很难说。如果启用跟踪,您将很容易看到代码在哪里分配了大部分内存。

但是您的实现可能会引起两件事:

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