Go 的多线程方法与其他方法(例如 pthread、boost::thread 或 Java 线程)有什么区别?
4 回答
引用自第 3 天教程<- 阅读此内容以获取更多信息。
Goroutines 根据需要被多路复用到系统线程上。当一个 goroutine 执行阻塞系统调用时,没有其他 goroutine 被阻塞。
我们会在某个时候对 CPU 绑定的 goroutine 做同样的事情,但是现在,如果你想要用户级并行,你必须设置 $GOMAXPROCS。或调用 runtime.GOMAXPROCS(n)。
一个 goroutine 不一定对应一个 OS 线程。它可以具有较小的初始堆栈大小,并且堆栈将根据需要增长。
需要时,可以将多个 gorouitine 多路复用到单个线程中。
更重要的是,这个概念如上所述,goroutine 是一个顺序程序,它可能会阻塞自己但不会阻塞其他 goroutine。
Goroutines 在 gccgo 中被实现为 pthreads,因此它也可以与 OS 线程相同。它将操作系统线程的概念与我们在编程时对多线程的思考分开。
IMO,Go 中的多线程吸引人的是通信设施:与 pthread 不同,pthread 必须构建通信基础设施(互斥体、队列等),在 Go 中,它默认以一种方便的形式提供。
简而言之,由于良好的通信设施(如果我可以这么说,类似于 Erlang),使用线程有“低摩擦” 。
在参考编译器 (5g/6g/8g) 中,主调度程序 ( src/pkg/runtime/proc.c ) 创建 N 个 OS 线程,其中 N 由 runtime.GOMAXPROCS(n) 控制(默认为 1)。每个调度程序线程从主列表中拉出一个新的 goroutine 并开始运行它。goroutine(s) 将继续运行,直到进行系统调用(例如 printf)或对通道进行操作,此时调度程序将获取下一个 goroutine 并从它停止的点运行它(参见gosched() 在src/pkg/runtime/chan.c中调用)。
出于所有意图和目的,调度是使用协程实现的。可以使用 setjmp() 和 longjmp() 直接用 C 语言编写相同的功能,Go(以及其他实现轻量级/绿色线程的语言)只是为您自动执行该过程。
轻量级线程的好处是因为它都是用户空间,创建一个“线程”非常便宜(分配一个小的默认堆栈)并且由于线程如何相互通信的固有结构而可以非常有效。缺点是它们不是真正的线程,这意味着单个轻量级线程可以阻塞整个程序,即使看起来所有线程都应该同时运行。
正如之前的答案所述,go 例程不一定对应于系统线程,但是如果您现在必须提高多线程的性能,我发现以下内容很有用:
默认情况下,Go 运行时的当前实现不会并行化此代码。它仅将一个内核专用于用户级处理。在系统调用中可以阻塞任意数量的 goroutine,但默认情况下,任何时候只有一个可以执行用户级代码。它应该更智能,而且有一天它会更智能,但是在你想要 CPU 并行性之前,你必须告诉运行时你想要同时执行代码的 goroutine 数量. 有两种相关的方法可以做到这一点。将环境变量 GOMAXPROCS 设置为要使用的内核数运行您的作业,或者导入运行时包并调用 runtime.GOMAXPROCS(NCPU)。一个有用的值可能是 runtime.NumCPU(),它报告本地机器上逻辑 CPU 的数量。同样,随着调度和运行时间的改进,预计此要求将被淘汰。
一个最大化我的 i5 处理器的示例程序是这个(在 htop 中以 100% 使用所有 4 个内核):
package main
import (
"fmt"
"time"
"runtime"
)
func main() {
runtime.GOMAXPROCS(4) // Set the maximum number of threads/processes
d := make(chan string)
go boring("boring!", d, 1)
go boring("boring!", d, 2)
go boring("boring!", d, 3)
go boring("boring!", d, 4)
for i := 0; i < 10; i++ {
time.Sleep(time.Second);
}
fmt.Println("You're boring; I'm leaving.")
}
func boring(msg string, c chan string, id int) {
for i := 0; ; i++ {
}
}
现在这实际上并没有“做”任何事情,但看看与用其他语言(如 Java)编写多线程应用程序相比,它有多短/容易/简单。