我的 Go 程序如何让所有 CPU 内核保持忙碌状态?

Posted

技术标签:

【中文标题】我的 Go 程序如何让所有 CPU 内核保持忙碌状态?【英文标题】:How can my Go program keep all the CPU cores busy? 【发布时间】:2013-07-25 20:45:36 【问题描述】:

Goroutines 是轻量级的进程,由 Go 运行时自动时间分片到一个或多个操作系统线程上。 (这是 Go 的一个非常酷的功能!)

假设我有一个像网络服务器这样的并发应用程序。在我的假设程序中同时发生了很多事情,没有太多的非并发(阿姆达尔定律)比率。

目前使用的操作系统线程的默认数量似乎是1。这是否意味着只有一个CPU核心被使用?

如果我开始我的程序

runtime.GOMAXPROCS(runtime.NumCPU())

这会合理有效地使用我 PC 上的所有内核吗?

使用更多操作系统线程是否有任何“并行松弛”的好处,例如通过一些启发式方法

runtime.GOMAXPROCS(runtime.NumCPU() * 2)

?

【问题讨论】:

【参考方案1】:

来自 Go 常见问题解答:

为什么我的多协程程序不使用多个 CPU?

您必须设置 GOMAXPROCS shell 环境变量或使用运行时包的同名函数,以允许运行时支持使用多个操作系统线程。

执行并行计算的程序应该受益于 GOMAXPROCS 的增加。但是,请注意并发不是并行。

(2015 年 8 月 28 日更新:Go 1.5 设置为使 GOMAXPROCS 的默认值与您机器上的 CPU 数量相同,因此这应该不再是问题了)

为什么使用 GOMAXPROCS > 1 有时会使我的程序变慢?

这取决于您的程序的性质。本质上是顺序的问题不能通过添加更多的 goroutine 来加速。只有当问题本质上是并行时,并发才会变成并行。

实际上,在使用多个 OS 线程时,在通道上进行通信而不是在计算上花费更多时间的程序会出现性能下降。这是因为在线程之间发送数据涉及切换上下文,这具有很大的成本。例如,Go 规范中的素筛示例虽然启动了许多 goroutine,但并没有显着的并行性;增加 GOMAXPROCS 更有可能减慢速度而不是加快速度。

Go 的 goroutine 调度器并没有它需要的那么好。将来,它应该识别这种情况并优化其对操作系统线程的使用。目前,GOMAXPROCS 应该基于每个应用程序设置。

简而言之:让 Go 使用“有效使用所有内核”是非常困难的。简单地生成十亿个 goroutine 并增加 GOMAXPROCS 可能会降低性能,因为它会一直切换线程上下文。如果您有一个可并行化的大型程序,那么将 GOMAXPROCS 增加到并行组件的数量就可以了。如果您在很大程度上非并行程序中嵌入了并行问题,它可能会加速,或者您可能必须创造性地使用诸如 runtime.LockOSThread() 之类的函数来确保运行时正确分发所有内容(一般来说,Go 只是愚蠢地传播当前非阻塞 Goroutines 在所有活动线程中随意且均匀)。

另外,GOMAXPROCS 是要使用的 CPU 内核数,如果它大于 NumCPU,我很确定它只是钳制到 NumCPU。 GOMAXPROCS 并不严格等于线程数。我不能 100% 确定运行时决定产生新线程的确切时间,但一个实例是当使用 runtime.LockOSThread() 的阻塞 goroutine 的数量大于或等于 GOMAXPROCs 时——它会产生比核心更多的线程所以它可以保持程序的其余部分正常运行。

基本上,增加 GOMAXPROCS 并使 go 使用 CPU 的所有内核非常简单。在 Go 开发的这个阶段,真正让它智能高效地使用你的 CPU 的所有内核是另一回事,需要大量的程序设计和调试才能做到正确。

【讨论】:

我知道运行时说什么,在这里我被告知不同:groups.google.com/forum/#!topic/golang-nuts/z_7F6EpcCLY 也许它依赖于实现? 啊!这是一个有用的线程。 “有一个论点是 Go 调度程序总是为短期请求保留一个备用线程。如果在最后 N 毫秒内没有正在运行的 goroutine 放弃控制,则分叉一个新线程,而不是等待计算绑定的线程完成。如果有计算密集型任务,每个 CPU 一个线程不是一个好规则。因此,在这种情况下,建议使用一些多余的线程。 @Rick-777,您在引号中所说的话:这是事实还是对新功能的提倡? 假设我有 1000 个独立的任务,并且想知道我应该使用多少个 goroutine 来处理这些任务。 GOMAXPROCS? GOMAXPROCS - 1? sehr 直觉解释!【参考方案2】:

这个问题无法回答,太宽泛了。

考虑你的问题、你的算法和你的工作量,然后衡量什么最适合这种组合。

没有人能回答这样的问题:“有没有什么启发式的方法是在我的午餐中添加两倍的盐会使它的味道更好?”因为这取决于午餐(西红柿比草莓更能从盐中受益)你的口味以及已经有多少盐。试试看。

关于更多:runtime.GOMAXPROCS(runtime.NumCPU()) 已达到崇拜状态,但通过从外部设置 GOMAXPROCS 环境变量来控制线程数可能是更好的选择。

【讨论】:

人们肯定已经从编写网络服务器和类似的高并发应用程序中获得了经验吗?【参考方案3】:

runtime.GOMAXPROCS() 设置您的程序可以同时使用的(虚拟)CPU 内核的数量。允许 Go 使用比实际更多的 CPU 内核无济于事,因为您的系统只有这么多 CPU 内核。

为了在多个线程中运行,您的程序必须有多个 goroutine,通常使用 go someFunc() 进行函数调用。如果您的程序不启动任何额外的 goroutine,那么无论您允许它使用多少 CPU/内核,它自然只会在一个线程中运行。

查看this 和以下有关如何创建 goroutine 的练习。

【讨论】:

允许 Go 使用比 CPU 更多的操作系统线程有时可能会减少延迟,因为如果任何线程应该被阻塞(无论多么短暂),可能会有更多的“准备运行”。 @Rick-777:当然。但是,设置runtime.GOMAXPROCS 不会改变程序可以运行的线程数。它改变了这些线程可以运行的 CPU 数量。查看runtime.NumGoroutine 以查看您的程序产生了多少线程/goroutine。

以上是关于我的 Go 程序如何让所有 CPU 内核保持忙碌状态?的主要内容,如果未能解决你的问题,请参考以下文章

使用所有可用的内核和 CPU 是不是不负责任?

什么可能导致我的程序在一段时间后不使用所有内核?

高速缓存和主内存之间如何保持数据一致性

在多处理中如何将 CPU 内核分配给 python 进程?

为啥 pmap|reducers/map 不使用所有 cpu 内核?

cpu 如何向内核发送有关硬件异常的信号?