第十四章上半部分 (#836)

Co-authored-by: Joe Chen <jc@unknwon.io>
This commit is contained in:
Haigang Zhou
2022-05-17 16:24:13 +08:00
committed by GitHub
parent 92d91dfa1c
commit f08d76efa6
11 changed files with 112 additions and 114 deletions

View File

@@ -6,7 +6,7 @@
并行是一种通过使用多处理器以提高速度的能力。所以并发程序可以是并行的,也可以不是。
公认的,使用多线程的应用难以做到准确,最主要的问题是内存中的数据共享,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果(称作 `竞态`)。
公认的,使用多线程的应用难以做到准确,最主要的问题是内存中的数据共享,它们会被多线程以无法预知的方式进行操作,导致一些无法重现或者随机的结果(称作*竞态*)。
**不要使用全局变量或者共享内存,它们会给你的代码在并发运算的时候带来危险。**
@@ -48,17 +48,17 @@ Go 的并发原语提供了良好的并发设计基础:表达程序结构以
## 14.1.3 使用 GOMAXPROCS
在 gc 编译器下6g 或者 8g你必须设置 GOMAXPROCS 为一个大于默认值 1 的数值来允许运行时支持使用多于 1 个的操作系统线程,所有的协程都会共享同一个线程除非将 GOMAXPROCS 设置为一个大于 1 的数。当 GOMAXPROCS 大于 1 时,会有一个线程池管理许多的线程。通过 `gccgo` 编译器 GOMAXPROCS 有效的与运行中的协程数量相等。假设 n 是机器上处理器或者核心的数量。如果你设置环境变量 GOMAXPROCS>=n或者执行 `runtime.GOMAXPROCS(n)`,接下来协程会被分割(分散)到 n 个处理器上。更多的处理器并不意味着性能的线性提升。有这样一个经验法则,对于 n 个核心的情况设置 GOMAXPROCS 为 n-1 以获得最佳性能,也同样需要遵守这条规则:协程的数量 > 1 + GOMAXPROCS > 1。
在 gc 编译器下6g 或者 8g你必须设置 `GOMAXPROCS` 为一个大于默认值 `1` 的数值来允许运行时支持使用多于 1 个的操作系统线程,所有的协程都会共享同一个线程除非将 `GOMAXPROCS` 设置为一个大于 1 的数。当 `GOMAXPROCS` 大于 1 时,会有一个线程池管理许多的线程。通过 `gccgo` 编译器 `GOMAXPROCS` 有效的与运行中的协程数量相等。假设 `n` 是机器上处理器或者核心的数量。如果你设置环境变量 `GOMAXPROCS>=n`,或者执行 `runtime.GOMAXPROCS(n)`,接下来协程会被分割(分散)到 `n` 个处理器上。更多的处理器并不意味着性能的线性提升。有这样一个经验法则,对于 n 个核心的情况设置 `GOMAXPROCS``n-1` 以获得最佳性能,也同样需要遵守这条规则:协程的数量 > `1 + GOMAXPROCS` > 1。
所以如果在某一时间只有一个协程在执行,不要设置 GOMAXPROCS
所以如果在某一时间只有一个协程在执行,不要设置 `GOMAXPROCS`
还有一些通过实验观察到的现象:在一台 1 颗 CPU 的笔记本电脑上,增加 GOMAXPROCS 到 9 会带来性能提升。在一台 32 核的机器上,设置 GOMAXPROCS=8 会达到最好的性能,在测试环境中,更高的数值无法提升性能。如果设置一个很大的 GOMAXPROCS 只会带来轻微的性能下降;设置 GOMAXPROCS=100使用 `top` 命令和 `H` 选项查看到只有 7 个活动的线程。
还有一些通过实验观察到的现象:在一台 1 颗 CPU 的笔记本电脑上,增加 `GOMAXPROCS` 到 9 会带来性能提升。在一台 32 核的机器上,设置 `GOMAXPROCS=8` 会达到最好的性能,在测试环境中,更高的数值无法提升性能。如果设置一个很大的 `GOMAXPROCS` 只会带来轻微的性能下降;设置 `GOMAXPROCS=100`,使用 `top` 命令和 `H` 选项查看到只有 7 个活动的线程。
增加 GOMAXPROCS 的数值对程序进行并发计算是有好处的;
增加 `GOMAXPROCS` 的数值对程序进行并发计算是有好处的;
请看 [goroutine_select2.go](examples/chapter_14/goroutine_select2.go)
总结GOMAXPROCS 等同于(并发的)线程数量,在一台核心数多于1个的机器上,会尽可能有等同于核心数的线程在并行运行。
总结:`GOMAXPROCS` 等同于(并发的)线程数量,在一台核心数多于 1 个的机器上,会尽可能有等同于核心数的线程在并行运行。
## 14.1.4 如何用命令行指定使用的核心数量
@@ -73,7 +73,7 @@ flag.Parse()
runtime.GOMAXPROCS(*numCores)
```
协程可以通过调用`runtime.Goexit()`来停止,尽管这样做几乎没有必要。
协程可以通过调用 `runtime.Goexit()` 来停止,尽管这样做几乎没有必要。
示例 14.1-[goroutine1.go](examples/chapter_14/goroutine1.go) 介绍了概念:
@@ -120,7 +120,7 @@ End of longWait()
At the end of main() // after 10s
```
`main()``longWait()``shortWait()` 三个函数作为独立的处理单元按顺序启动,然后开始并行运行。每一个函数都在运行的开始和结束阶段输出了消息。为了模拟他们运算的时间消耗,我们使用了 `time` 包中的 `Sleep` 函数。`Sleep()` 可以按照指定的时间来暂停函数或协程的执行,这里使用了纳秒(ns,符号 1e9 表示 1 乘 10 的 9 次方,e=指数)。
`main()``longWait()``shortWait()` 三个函数作为独立的处理单元按顺序启动,然后开始并行运行。每一个函数都在运行的开始和结束阶段输出了消息。为了模拟他们运算的时间消耗,我们使用了 `time` 包中的 `Sleep` 函数。`Sleep()` 可以按照指定的时间来暂停函数或协程的执行,这里使用了纳秒(`ns`,符号 `1e9` 表示 1 乘 10 的 9 次方,`e`=指数)。
他们按照我们期望的顺序打印出了消息,几乎都一样,可是我们明白这是模拟出来的,以并行的方式。我们让 `main()` 函数暂停 10 秒从而确定它会在另外两个协程之后结束。如果不这样(如果我们让 `main()` 函数停止 4 秒),`main()` 会提前结束,`longWait()` 则无法完成。如果我们不在 `main()` 中等待,协程会随着程序的结束而消亡。
@@ -128,7 +128,7 @@ At the end of main() // after 10s
另外,协程是独立的处理单元,一旦陆续启动一些协程,你无法确定他们是什么时候真正开始执行的。你的代码逻辑必须独立于协程调用的顺序。
为了对比使用一个线程,连续调用的情况,移除 go 关键字,重新运行程序。
为了对比使用一个线程,连续调用的情况,移除 `go` 关键字,重新运行程序。
现在输出:
@@ -146,11 +146,11 @@ At the end of main() // after 17 s
将数组分割为若干个不重复的切片,然后给每一个切片启动一个协程进行查找计算。这样许多并行的协程可以用来进行查找任务,整体的查找时间会缩短(除以协程的数量)。
## 14.1.5 Go 协程goroutines和协程coroutines
## 14.1.5 Go 协程 (goroutines) 和协程 (coroutines)
译者注标题中的“Go协程goroutines)” 即是 14 章讲的协程指的是 Go 语言中的协程。而“协程coroutines”指的是其他语言中的协程概念,仅在本节出现。)
译者注标题中的“Go协程 (goroutines)”即是 14 章讲的协程指的是 Go 语言中的协程。而“协程(coroutines)”指的是其他语言中的协程概念,仅在本节出现。)
在其他语言中,比如 C#Lua 或者 Python 都有协程的概念。这个名字表明它和 Go协程有些相似不过有两点不同
在其他语言中,比如 C#Lua 或者 Python 都有协程的概念。这个名字表明它和 Go 协程有些相似,不过有两点不同:
- Go 协程意味着并行(或者可以以并行的方式部署),协程一般来说不是这样的
- Go 协程通过通道来通信;协程通过让出和恢复操作来通信
@@ -160,5 +160,5 @@ Go 协程比协程更强大,也很容易从协程的逻辑复用到 Go 协程
## 链接
- [目录](directory.md)
- 上一节:[协程goroutine与通道channel](14.0.md)
- 上一节:[协程 (goroutine) 与通道 (channel)](14.0.md)
- 下一节:[使用通道进行协程间通信](14.2.md)