Nodejs EventLoop(带集群模块)和Golang Scheduler的比较

Comparison of Nodejs EventLoop (with cluster module) and Golang Scheduler

在 nodejs 中,主要批评者基于其 单线程事件循环模型。

nodejs 最大的缺点是无法在应用程序中执行 CPU 密集型任务。出于演示目的,让我们以 while 循环为例(这可能类似于返回十万条记录的 db 函数,然后在 nodejs 中处理这些记录。)

while(1){
    x++
}

此类代码将阻塞主堆栈,因此事件队列中等待的所有其他任务将永远没有机会执行。 (而在 web 应用程序中,新用户将无法连接到该应用程序)。

然而,可以使用像 cluster 这样的模块来利用多核系统并部分解决上述问题。 Cluster 模块允许创建一个由独立进程组成的小型网络,这些进程可以共享服务器端口,这使 Node.js 应用程序可以访问服务器的全部功能。 (但是,使用 Cluster 的最大缺点之一是无法在应用程序代码中维护状态)。 但是,如果服务器负载过大,我们很有可能再次陷入同样的​​情况(如上所述)。

当我开始学习Go语言并了解它的架构和goroutines时,我认为它可能会解决由于nodejs的单线程事件循环模型而出现的问题。并且它可能会避免上述 CPU 密集任务的场景,直到我遇到这段有趣的代码,它阻止了所有 GO 应用程序并且什么也没有发生,就像 nodejs 中的 while 循环一样。

func main() {
    var x int
    threads := runtime.GOMAXPROCS(0)
    for i := 0; i < threads; i++ {
        go func() {
            for { x++ }
        }()
    }
    time.Sleep(time.Second)
    fmt.Println("x =", x)
}
//or perhaps even if we use some number that is just greater than the threads.

所以,问题是,如果我有一个负载密集型应用程序,并且还会有很多 CPU 密集型任务,我可能会陷入上述情况。 (其中 db returns 大量行,然后应用程序需要处理和修改这些行中的某些内容)。传入的用户不会被阻止,所有其他任务也会被阻止吗?

那么,如何解决上述问题呢?

P.S
或许,我提到的用例没有多大意义? :)

目前(Go 1.11及更早版本)你所谓的 紧密循环确实会阻塞代码。 发生这种情况仅仅是因为当前 Go 编译器 插入执行 "preemption checks" 的代码(«我应该放弃 到调度器,所以它 运行 是另一个 goroutine?») 仅在 它编译的函数的序言(几乎,但我们不要离题)。 如果您的循环不调用任何函数,则不会进行抢占检查 将制作。

Go 开发者很清楚这一点 和 are working on eventually alleviating this issue.

不过请注意,您所谓的问题在 最真实的场景:执行很长时间的代码 运行 秒的 CPU 密集工作 没有调用任何函数 很少见,而且介于两者之间。

在某些情况下,您确实有这样的代码并且 detected 真的让其他goroutine饿死 (让我强调一下:你已经通过分析发现了这一点——作为 而不是只是想出 "it must be slow"),你可能 应用几种技术来处理这个问题:

  • 在某些关键点插入对runtime.Gosched()的调用 您的 long-运行ning CPU 密集型代码。 这将强制放弃对另一个 goroutine 的控制 而实际上并没有挂起调用者 goroutine(所以它会 运行 将再次安排)。
  • 为 goroutines 提供 OS 个线程 运行ning 那些 CPU 猪:
    1. 将一组这样的 CPU 猪绑定到,比如说,N "worker goroutines";
    2. 在他们前面放一个调度员(这叫"fan-out");
    3. 确保 N 明显小于 runtime.GOMAXPROCS 提高后者,这样你就有了那些N额外的线程。
    4. 通过调度程序将工作单元铲到那些专用的 goroutines。