mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-01-25 23:11:02 +08:00
Merge branch 'LCTT/master'
This commit is contained in:
commit
f4992eb37e
@ -1,19 +1,18 @@
|
||||
一些常见的并发编程错误
|
||||
============================================================
|
||||
|
||||
Go 是一个内置支持并发编程的语言。借助使用 `go` 关键字去创建 goroutines(轻量级线程)和在 Go 中提供的 [使用][8] [信道][9] 和 [其它的并发][10] [同步方法][11],使得并发编程变得很容易、很灵活和很有趣。
|
||||
Go 是一个内置支持并发编程的语言。借助使用 `go` 关键字去创建<ruby>协程<rt>goroutine</rt></ruby>(轻量级线程)和在 Go 中提供的 [使用][8] [信道][9] 和 [其它的并发][10] [同步方法][11],使得并发编程变得很容易、很灵活和很有趣。
|
||||
|
||||
另一方面,Go 并不会阻止一些因 Go 程序员粗心大意或者缺乏经验而造成的并发编程错误。在本文的下面部分将展示一些在 Go 编程中常见的并发编程错误,以帮助 Go 程序员们避免再犯类似的错误。
|
||||
|
||||
### 需要同步的时候没有同步
|
||||
|
||||
代码行或许 [没有按出现的顺序运行][2]。
|
||||
代码行或许 [不是按出现的顺序运行的][2]。
|
||||
|
||||
在下面的程序中有两个错误。
|
||||
|
||||
* 第一,在 main goroutine 中读取 `b` 和在新的 goroutine 中写入 `b` 可能导致数据争用。
|
||||
|
||||
* 第二,条件 `b == true` 并不能保证在 main goroutine 中的 `a != nil`。在新的 goroutine 中编译器和 CPU 可能会通过 [重排序指令][1] 进行优化,因此,在运行时 `b` 赋值可能发生在 `a` 赋值之前,在 main goroutine 中当 `a` 被修改后,它将会让部分 `a` 一直保持为 `nil`。
|
||||
* 第一,在 `main` 协程中读取 `b` 和在新的 协程 中写入 `b` 可能导致数据争用。
|
||||
* 第二,条件 `b == true` 并不能保证在 `main` 协程 中的 `a != nil`。在新的协程中编译器和 CPU 可能会通过 [重排序指令][1] 进行优化,因此,在运行时 `b` 赋值可能发生在 `a` 赋值之前,在 `main` 协程 中当 `a` 被修改后,它将会让部分 `a` 一直保持为 `nil`。
|
||||
|
||||
```
|
||||
package main
|
||||
@ -39,7 +38,6 @@ func main() {
|
||||
}
|
||||
a[0], a[1], a[2] = 0, 1, 2 // might panic
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
上面的程序或者在一台计算机上运行的很好,但是在另一台上可能会引发异常。或者它可能运行了 _N_ 次都很好,但是可能在第 _(N+1)_ 次引发了异常。
|
||||
@ -70,7 +68,7 @@ func main() {
|
||||
我们先来看一个简单的例子。
|
||||
|
||||
```
|
||||
ppackage main
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@ -118,7 +116,6 @@ func main() {
|
||||
num = 789
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
你认为程序的预期输出是什么?`123` 还是 `789`?事实上它的输出与编译器有关。对于标准的 Go 编译器 1.10 来说,这个程序很有可能输出是 `123`。但是在理论上,它可能输出的是 `789`,或者其它的随机数。
|
||||
@ -127,7 +124,7 @@ func main() {
|
||||
|
||||
是的,在上面的程序中存在数据争用。表达式 `*p` 可能会被先计算、后计算、或者在处理赋值语句 `num = 789` 时计算。`time.Sleep` 调用并不能保证 `*p` 发生在赋值语句处理之前进行。
|
||||
|
||||
对于这个特定的示例,我们将在新的 goroutine 创建之前,将值保存到一个临时值中,然后在新的 goroutine 中使用临时值去消除数据争用。
|
||||
对于这个特定的示例,我们将在新的协程创建之前,将值保存到一个临时值中,然后在新的协程中使用临时值去消除数据争用。
|
||||
|
||||
```
|
||||
...
|
||||
@ -136,26 +133,21 @@ func main() {
|
||||
c <- tmp
|
||||
}()
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
### 使 Goroutines 挂起
|
||||
### 使协程挂起
|
||||
|
||||
挂起 goroutines 是指让 goroutines 一直处于阻塞状态。导致 goroutines 被挂起的原因很多。比如,
|
||||
挂起协程是指让协程一直处于阻塞状态。导致协程被挂起的原因很多。比如,
|
||||
|
||||
* 一个 goroutine 尝试从一个 nil 信道中或者从一个没有其它 goroutines 给它发送值的信道中检索数据。
|
||||
* 一个协程尝试从一个 nil 信道中或者从一个没有其它协程给它发送值的信道中检索数据。
|
||||
* 一个协程尝试去发送一个值到 nil 信道,或者发送到一个没有其它的协程接收值的信道中。
|
||||
* 一个协程被它自己死锁。
|
||||
* 一组协程彼此死锁。
|
||||
* 当运行一个没有 `default` 分支的 `select` 代码块时,一个协程被阻塞,以及在 `select` 代码块中 `case` 关键字后的所有信道操作保持阻塞状态。
|
||||
|
||||
* 一个 goroutine 尝试去发送一个值到 nil 信道,或者发送到一个没有其它的 goroutines 接收值的信道中。
|
||||
除了有时我们为了避免程序退出,特意让一个程序中的 `main` 协程保持挂起之外,大多数其它的协程挂起都是意外情况。Go 运行时很难判断一个协程到底是处于挂起状态还是临时阻塞。因此,Go 运行时并不会去释放一个挂起的协程所占用的资源。
|
||||
|
||||
* 一个 goroutine 被它自己死锁。
|
||||
|
||||
* 一组 goroutines 彼此死锁。
|
||||
|
||||
* 当运行一个没有 `default` 分支的 `select` 代码块时,一个 goroutine 被阻塞,以及在 `select` 代码块中 `case` 关键字后的所有信道操作保持阻塞状态。
|
||||
|
||||
除了有时我们为了避免程序退出,特意让一个程序中的 main goroutine 保持挂起之外,大多数其它的 goroutine 挂起都是意外情况。Go 运行时很难判断一个 goroutine 到底是处于挂起状态还是临时阻塞。因此,Go 运行时并不会去释放一个挂起的 goroutine 所占用的资源。
|
||||
|
||||
在 [谁先响应谁获胜][12] 的信道使用案例中,如果使用的 Future 信道容量不够大,当尝试向 Future 信道发送结果时,一些响应较慢的信道将被挂起。比如,如果调用下面的函数,将有 4 个 goroutine 处于永远阻塞状态。
|
||||
在 [谁先响应谁获胜][12] 的信道使用案例中,如果使用的 future 信道容量不够大,当尝试向 Future 信道发送结果时,一些响应较慢的信道将被挂起。比如,如果调用下面的函数,将有 4 个协程处于永远阻塞状态。
|
||||
|
||||
```
|
||||
func request() int {
|
||||
@ -168,12 +160,11 @@ func request() int {
|
||||
}
|
||||
return <-c
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
为避免这 4 个 goroutines 一直处于挂起状态, `c` 信道的容量必须至少是 `4`。
|
||||
为避免这 4 个协程一直处于挂起状态, `c` 信道的容量必须至少是 `4`。
|
||||
|
||||
在 [实现谁先响应谁获胜的第二种方法][13] 的信道使用案例中,如果将 future 信道用做非缓冲信道,那么有可能这个信息将永远也不会有响应并挂起。例如,如果在一个 goroutine 中调用下面的函数,goroutine 可能会挂起。原因是,如果接收操作 `<-c` 准备就绪之前,五个发送操作全部尝试发送,那么所有的尝试发送的操作将全部失败,因此那个调用者 goroutine 将永远也不会接收到值。
|
||||
在 [实现谁先响应谁获胜的第二种方法][13] 的信道使用案例中,如果将 future 信道用做非缓冲信道,那么有可能这个信息将永远也不会有响应而挂起。例如,如果在一个协程中调用下面的函数,协程可能会挂起。原因是,如果接收操作 `<-c` 准备就绪之前,五个发送操作全部尝试发送,那么所有的尝试发送的操作将全部失败,因此那个调用者协程将永远也不会接收到值。
|
||||
|
||||
```
|
||||
func request() int {
|
||||
@ -189,10 +180,9 @@ func request() int {
|
||||
}
|
||||
return <-c
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
将信道 `c` 变成缓冲信道将保证五个发送操作中的至少一个操作会发送成功,这样,上面函数中的那个调用者 goroutine 将不会被挂起。
|
||||
将信道 `c` 变成缓冲信道将保证五个发送操作中的至少一个操作会发送成功,这样,上面函数中的那个调用者协程将不会被挂起。
|
||||
|
||||
### 在 `sync` 标准包中拷贝类型值
|
||||
|
||||
@ -234,7 +224,7 @@ func (c Counter) Value() (r int64) {
|
||||
|
||||
### 在错误的地方调用 `sync.WaitGroup` 的方法
|
||||
|
||||
每个 `sync.WaitGroup` 值维护一个内部计数器,这个计数器的初始值为 0。如果一个 `WaitGroup` 计数器的值也是 0,调用 `WaitGroup` 值的 `Wait` 方法不会被阻塞,否则,在计数器值为 0 之前,这个调用会一直被阻塞。
|
||||
每个 `sync.WaitGroup` 值维护一个内部计数器,这个计数器的初始值为 0。如果一个 `WaitGroup` 计数器的值是 0,调用 `WaitGroup` 值的 `Wait` 方法就不会被阻塞,否则,在计数器值为 0 之前,这个调用会一直被阻塞。
|
||||
|
||||
为了让 `WaitGroup` 值的使用有意义,当一个 `WaitGroup` 计数器值为 0 时,必须在相应的 `WaitGroup` 值的 `Wait` 方法调用之前,去调用 `WaitGroup` 值的 `Add` 方法。
|
||||
|
||||
@ -267,7 +257,7 @@ func main() {
|
||||
|
||||
```
|
||||
|
||||
为让程序的表现符合预期,在 `for` 循环中,我们将把 `Add` 方法的调用移动到创建的新 goroutines 的范围之外,修改后的代码如下。
|
||||
为让程序的表现符合预期,在 `for` 循环中,我们将把 `Add` 方法的调用移动到创建的新协程的范围之外,修改后的代码如下。
|
||||
|
||||
```
|
||||
...
|
||||
@ -279,16 +269,14 @@ func main() {
|
||||
}()
|
||||
}
|
||||
...
|
||||
|
||||
```
|
||||
|
||||
### 不正确使用 Futures 信道
|
||||
### 不正确使用 futures 信道
|
||||
|
||||
在 [信道使用案例][14] 的文章中,我们知道一些函数将返回 [futures 信道][15]。假设 `fa` 和 `fb` 就是这样的两个函数,那么下面的调用就使用了不正确的 future 参数。
|
||||
|
||||
```
|
||||
doSomethingWithFutureArguments(<-fa(), <-fb())
|
||||
|
||||
```
|
||||
|
||||
在上面的代码行中,两个信道接收操作是顺序进行的,而不是并发的。我们做如下修改使它变成并发操作。
|
||||
@ -296,12 +284,11 @@ doSomethingWithFutureArguments(<-fa(), <-fb())
|
||||
```
|
||||
ca, cb := fa(), fb()
|
||||
doSomethingWithFutureArguments(<-c1, <-c2)
|
||||
|
||||
```
|
||||
|
||||
### 没有等 Goroutine 的最后的活动的发送结束就关闭信道
|
||||
### 没有等协程的最后的活动的发送结束就关闭信道
|
||||
|
||||
Go 程序员经常犯的一个错误是,还有一些其它的 goroutine 可能会发送值到以前的信道时,这个信道就已经被关闭了。当这样的发送(发送到一个已经关闭的信道)真实发生时,将引发一个异常。
|
||||
Go 程序员经常犯的一个错误是,还有一些其它的协程可能会发送值到以前的信道时,这个信道就已经被关闭了。当这样的发送(发送到一个已经关闭的信道)真实发生时,将引发一个异常。
|
||||
|
||||
这种错误在一些以往的著名 Go 项目中也有发生,比如在 Kubernetes 项目中的 [这个 bug][3] 和 [这个 bug][4]。
|
||||
|
||||
@ -309,7 +296,7 @@ Go 程序员经常犯的一个错误是,还有一些其它的 goroutine 可能
|
||||
|
||||
### 在值上做 64 位原子操作时没有保证值地址 64 位对齐
|
||||
|
||||
到目前为止(Go 1.10),在标准的 Go 编译器中,在一个 64 位原子操作中涉及到的值的地址要求必须是 64 位对齐的。如果没有对齐则导致当前的 goroutine 异常。对于标准的 Go 编译器来说,这种失败仅发生在 32 位的架构上。请阅读 [内存布局][6] 去了解如何在一个 32 位操作系统上保证 64 位对齐。
|
||||
到目前为止(Go 1.10),在标准的 Go 编译器中,在一个 64 位原子操作中涉及到的值的地址要求必须是 64 位对齐的。如果没有对齐则导致当前的协程异常。对于标准的 Go 编译器来说,这种失败仅发生在 32 位的架构上。请阅读 [内存布局][6] 去了解如何在一个 32 位操作系统上保证 64 位对齐。
|
||||
|
||||
### 没有注意到大量的资源被 `time.After` 函数调用占用
|
||||
|
||||
@ -335,7 +322,6 @@ func longRunning(messages <-chan string) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
为避免在上述代码中创建过多的 `Timer` 值,我们将使用一个单一的 `Timer` 值去完成同样的任务。
|
||||
@ -370,7 +356,7 @@ func longRunning(messages <-chan string) {
|
||||
|
||||
在 `select` 块的第一个 `case` 分支的结束部分,`time.Timer` 值被释放,因此,我们不需要去停止它。但是必须在第二个分支中停止定时器。如果在第二个分支中 `if` 代码块缺失,它可能至少在 `Reset` 方法调用时,会(通过 Go 运行时)发送到 `timer.C` 信道,并且那个 `longRunning` 函数可能会早于预期返回,对于 `Reset` 方法来说,它可能仅仅是重置内部定时器为 0,它将不会清理(耗尽)那个发送到 `timer.C` 信道的值。
|
||||
|
||||
例如,下面的程序很有可能在一秒内而不是十秒时退出。并且更重要的是,这个程序并不是 DRF 的(译者注:data race free,多线程程序的一种同步程度)。
|
||||
例如,下面的程序很有可能在一秒内而不是十秒时退出。并且更重要的是,这个程序并不是 DRF 的(LCTT 译注:data race free,多线程程序的一种同步程度)。
|
||||
|
||||
```
|
||||
package main
|
||||
@ -392,12 +378,11 @@ func main() {
|
||||
<-timer.C
|
||||
fmt.Println(time.Since(start)) // 1.000188181s
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
当 `time.Timer` 的值不再被其它任何一个东西使用时,它的值可能被停留在一种非停止状态,但是,建议在结束时停止它。
|
||||
|
||||
在多个 goroutines 中如果不按建议使用 `time.Timer` 值并发,可能会有 bug 隐患。
|
||||
在多个协程中如果不按建议使用 `time.Timer` 值并发,可能会有 bug 隐患。
|
||||
|
||||
我们不应该依赖一个 `Reset` 方法调用的返回值。`Reset` 方法返回值的存在仅仅是为了兼容性目的。
|
||||
|
||||
@ -405,9 +390,9 @@ func main() {
|
||||
|
||||
via: https://go101.org/article/concurrent-common-mistakes.html
|
||||
|
||||
作者:[go101.org ][a]
|
||||
作者:[go101.org][a]
|
||||
译者:[qhwdw](https://github.com/qhwdw)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,4 +1,7 @@
|
||||
# GDPR 将如何影响开源社区?
|
||||
GDPR 将如何影响开源社区?
|
||||
===========
|
||||
|
||||
> 许多组织正在争先恐后地了解隐私法的变化如何影响他们的工作。
|
||||
|
||||
![How will the GDPR impact open source communities?](https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/OSDC_EU_flag.png?itok=4n9j74tL "GDPR 法案将如何影响开源社区?")
|
||||
|
||||
@ -70,17 +73,16 @@ GDPR 赋予欧盟公民更多的权利。其中一项权利是向企业查询个
|
||||
|
||||
[![](https://opensource.com/sites/default/files/styles/profile_pictures/public/robinm-low-3-square.jpg?itok=8qH6iUZh)][7]
|
||||
|
||||
Robin Muilwijk \- Robin Muilwijk 是一名互联网和电子政务顾问,在 Red Hat 旗下在线发布平台 Opensource.com 担任社区版主,在 Open Organization 担任大使。此外,Robin 还是 eZ 社区董事会成员,[eZ 系统][8] 社区的管理员。Robin 活跃在社交媒体中,促进和支持商业和生活领域的开源项目。可以在 Twitter 上关注 [Robin Muilwijk][9] 以获取更多关于他的信息。
|
||||
Robin Muilwijk 是一名互联网和电子政务顾问,在 Red Hat 旗下在线发布平台 Opensource.com 担任社区版主,在 Open Organization 担任大使。此外,Robin 还是 eZ 社区董事会成员,[eZ 系统][8] 社区的管理员。Robin 活跃在社交媒体中,促进和支持商业和生活领域的开源项目。可以在 Twitter 上关注 [Robin Muilwijk][9] 以获取更多关于他的信息。
|
||||
|
||||
[更多关于我的信息][10]
|
||||
|
||||
* [学习如何做出贡献][11]
|
||||
|
||||
---
|
||||
|
||||
via: [https://opensource.com/article/18/4/gdpr-impact][12]
|
||||
|
||||
作者: [Robin Muilwijk][13] 选题者: [@lujun9972][14] 译者: [pinewall][15] 校对: [校对者ID][16]
|
||||
作者: [Robin Muilwijk][13] 选题者: [lujun9972][14] 译者: [pinewall][15] 校对: [wxy][16]
|
||||
|
||||
本文由 [LCTT][17] 原创编译,[Linux中国][18] 荣誉推出
|
||||
|
||||
@ -99,6 +101,6 @@ via: [https://opensource.com/article/18/4/gdpr-impact][12]
|
||||
[13]: https://opensource.com/users/robinmuilwijk
|
||||
[14]: https://github.com/lujun9972
|
||||
[15]: https://github.com/pinewall
|
||||
[16]: https://github.com/校对者ID
|
||||
[16]: https://github.com/wxy
|
||||
[17]: https://github.com/LCTT/TranslateProject
|
||||
[18]: https://linux.cn/
|
Loading…
Reference in New Issue
Block a user