Go非常适合并发,任务作为go例程传递,并且go例程在虚拟处理器中处理,每当一个go例程遇到阻塞操作(数据库调用)时,go例程便移至另一虚拟处理器在不同的线程上(通常大部分时间在另一个物理处理器上运行)…现在,我们实现了并行性。
Node.js具有类似的技术(除了所有事情都发生在同一线程上),但是将所有正在等待的虚拟进程放在等待队列中,直到它们接收到来自阻塞资源(DB,URL)的响应,然后将其发送以进行处理。
Node.js的不足之处在于它无法处理处理器密集型操作(例如循环),并且正在运行的虚拟过程将一直持续到完成而没有抢占,这就是为什么Node.js被视为明智之举尽管它具有高并发可用性,但在关键系统中使用它之前。
是的,Go产生了一个新的线程来处理阻塞的go例程,但是处理器密集型操作又如何呢?它们被视为相同,还是遭受Node问题的困扰?
是的,但是在实践中要比使用Node困难得多,并且从中恢复也要容易得多。节点是单线程的,除非您编写显式的多进程代码(这并不总是那么容易,特别是如果您希望具有可移植性)。Go使用N:M调度,并且具有一定数量的正在运行的最大线程数(默认情况下等于逻辑CPU的数量,但是可调)。注意 running :等待阻塞操作的goroutine被“冻结”,不算在占用正在运行的线程中。
因此,如果您有一个goroutine进行CPU密集型工作,那么通常不会影响其他goroutine的运行能力,因为还有很多其他线程可以运行它们。如果您 所有 的goroutine都被计算所占据,那么其他程序确实没有机会运行,直到有人放弃了CPU。如果您实际上正在完成工作,那么这可能 不一定 会成为问题,因为您的所有CPU都在进行实际工作,但是当然有时可能在对延迟敏感的情况下。
如果有问题,可以想到三种解决方案:
runtime.Gosched在长时间计算期间使用,可以控制处理器,从而使其他goroutine可以运行。无需进行其他任何更改;这只是与合作调度程序一起工作的一种方式。Gosched可能会立即返回,也可能稍后返回。
runtime.Gosched
Gosched
使用工作池将并行的CPU密集型工作量限制为少于GOMAXPROCS。Go使这变得非常容易。
同一枚硬币的另一面:将GOMAXPROCS提升到并行计算任务的预期数量以上。这可能是最糟糕的主意,并且至少会在一定程度上影响调度,但是它仍然可以正常工作,并确保您有可用于处理事件的线程。