Merging other languages
This commit is contained in:
482
en/02.7.md
482
en/02.7.md
@@ -1,240 +1,242 @@
|
||||
# 2.7 并发
|
||||
|
||||
有人把Go比作21世纪的C语言,第一是因为Go语言设计简单,第二,21世纪最重要的就是并行程序设计,而Go从语言层面就支持了并行。
|
||||
|
||||
## goroutine
|
||||
|
||||
goroutine是Go并行设计的核心。goroutine说到底其实就是线程,但是它比线程更小,十几个goroutine可能体现在底层就是五六个线程,Go语言内部帮你实现了这些goroutine之间的内存共享。执行goroutine只需极少的栈内存(大概是4~5KB),当然会根据相应的数据伸缩。也正因为如此,可同时运行成千上万个并发任务。goroutine比thread更易用、更高效、更轻便。
|
||||
|
||||
goroutine是通过Go的runtime管理的一个线程管理器。goroutine通过`go`关键字实现了,其实就是一个普通的函数。
|
||||
|
||||
go hello(a, b, c)
|
||||
|
||||
通过关键字go就启动了一个goroutine。我们来看一个例子
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.Gosched()
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go say("world") //开一个新的Goroutines执行
|
||||
say("hello") //当前Goroutines执行
|
||||
}
|
||||
|
||||
// 以上程序执行后将输出:
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
// world
|
||||
// hello
|
||||
|
||||
我们可以看到go关键字很方便的就实现了并发编程。
|
||||
上面的多个goroutine运行在同一个进程里面,共享内存数据,不过设计上我们要遵循:不要通过共享来通信,而要通过通信来共享。
|
||||
|
||||
> runtime.Gosched()表示让CPU把时间片让给别人,下次某个时候继续恢复执行该goroutine。
|
||||
|
||||
>默认情况下,调度器仅使用单线程,也就是说只实现了并发。想要发挥多核处理器的并行,需要在我们的程序中显式调用 runtime.GOMAXPROCS(n) 告诉调度器同时使用多个线程。GOMAXPROCS 设置了同时运行逻辑代码的系统线程的最大数量,并返回之前的设置。如果n < 1,不会改变当前设置。以后Go的新版本中调度得到改进后,这将被移除。这里有一篇Rob介绍的关于并发和并行的文章:http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide
|
||||
|
||||
## channels
|
||||
goroutine运行在相同的地址空间,因此访问共享内存必须做好同步。那么goroutine之间如何进行数据的通信呢,Go提供了一个很好的通信机制channel。channel可以与Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel时,也需要定义发送到channel的值的类型。注意,必须使用make 创建channel:
|
||||
|
||||
ci := make(chan int)
|
||||
cs := make(chan string)
|
||||
cf := make(chan interface{})
|
||||
|
||||
channel通过操作符`<-`来接收和发送数据
|
||||
|
||||
ch <- v // 发送v到channel ch.
|
||||
v := <-ch // 从ch中接收数据,并赋值给v
|
||||
|
||||
我们把这些应用到我们的例子中来:
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func sum(a []int, c chan int) {
|
||||
total := 0
|
||||
for _, v := range a {
|
||||
total += v
|
||||
}
|
||||
c <- total // send total to c
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := []int{7, 2, 8, -9, 4, 0}
|
||||
|
||||
c := make(chan int)
|
||||
go sum(a[:len(a)/2], c)
|
||||
go sum(a[len(a)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
|
||||
fmt.Println(x, y, x + y)
|
||||
}
|
||||
|
||||
默认情况下,channel接收和发送数据都是阻塞的,除非另一端已经准备好,这样就使得Goroutines同步变的更加的简单,而不需要显式的lock。所谓阻塞,也就是如果读取(value := <-ch)它将会被阻塞,直到有数据接收。其次,任何发送(ch<-5)将会被阻塞,直到数据被读出。无缓冲channel是在多个goroutine之间同步很棒的工具。
|
||||
|
||||
## Buffered Channels
|
||||
上面我们介绍了默认的非缓存类型的channel,不过Go也允许指定channel的缓冲大小,很简单,就是channel可以存储多少元素。ch:= make(chan bool, 4),创建了可以存储4个元素的bool 型channel。在这个channel 中,前4个元素可以无阻塞的写入。当写入第5个元素时,代码将会阻塞,直到其他goroutine从channel 中读取一些元素,腾出空间。
|
||||
|
||||
ch := make(chan type, value)
|
||||
|
||||
当 value = 0 时,channel 是无缓冲阻塞读写的,当value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
|
||||
|
||||
我们看一下下面这个例子,你可以在自己本机测试一下,修改相应的value值
|
||||
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 2)//修改2为1就报错,修改2为3可以正常运行
|
||||
c <- 1
|
||||
c <- 2
|
||||
fmt.Println(<-c)
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
//修改为1报如下的错误:
|
||||
//fatal error: all goroutines are asleep - deadlock!
|
||||
|
||||
## Range和Close
|
||||
上面这个例子中,我们需要读取两次c,这样不是很方便,Go考虑到了这一点,所以也可以通过range,像操作slice或者map一样操作缓存类型的channel,请看下面的例子
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 1, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x + y
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
`for i := range c`能够不断的读取channel里面的数据,直到该channel被显式的关闭。上面代码我们看到可以显式的关闭channel,生产者通过内置函数`close`关闭channel。关闭channel之后就无法再发送任何数据了,在消费方可以通过语法`v, ok := <-ch`测试channel是否被关闭。如果ok返回false,那么说明channel已经没有任何数据并且已经被关闭。
|
||||
|
||||
>记住应该在生产者的地方关闭channel,而不是消费的地方去关闭它,这样容易引起panic
|
||||
|
||||
>另外记住一点的就是channel不像文件之类的,不需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的
|
||||
|
||||
## Select
|
||||
我们上面介绍的都是只有一个channel的情况,那么如果存在多个channel的时候,我们该如何操作呢,Go里面提供了一个关键字`select`,通过`select`可以监听channel上的数据流动。
|
||||
|
||||
`select`默认是阻塞的,只有当监听的channel中有发送或接收可以进行时才会运行,当多个channel都准备好的时候,select是随机的选择一个执行的。
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 1, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x + y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
}
|
||||
|
||||
在`select`里面还有default语法,`select`其实就是类似switch的功能,default就是当监听的channel都没有准备好的时候,默认执行的(select不再阻塞等待channel)。
|
||||
|
||||
select {
|
||||
case i := <-c:
|
||||
// use i
|
||||
default:
|
||||
// 当c阻塞的时候执行这里
|
||||
}
|
||||
|
||||
## 超时
|
||||
有时候会出现goroutine阻塞的情况,那么我们如何避免整个程序进入阻塞的情况呢?我们可以利用select来设置超时,通过如下的方式实现:
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
o := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case v := <- c:
|
||||
println(v)
|
||||
case <- time.After(5 * time.Second):
|
||||
println("timeout")
|
||||
o <- true
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
<- o
|
||||
}
|
||||
|
||||
|
||||
## runtime goroutine
|
||||
runtime包中有几个处理goroutine的函数:
|
||||
|
||||
- Goexit
|
||||
|
||||
退出当前执行的goroutine,但是defer函数还会继续调用
|
||||
|
||||
- Gosched
|
||||
|
||||
让出当前goroutine的执行权限,调度器安排其他等待的任务运行,并在下次某个时候从该位置恢复执行。
|
||||
|
||||
- NumCPU
|
||||
|
||||
返回 CPU 核数量
|
||||
|
||||
- NumGoroutine
|
||||
|
||||
返回正在执行和排队的任务总数
|
||||
|
||||
- GOMAXPROCS
|
||||
|
||||
用来设置可以并行计算的CPU核数的最大值,并返回之前的值。
|
||||
|
||||
|
||||
|
||||
## links
|
||||
* [目录](<preface.md>)
|
||||
* 上一章: [interface](<02.6.md>)
|
||||
* 下一节: [总结](<02.8.md>)
|
||||
# Concurrency
|
||||
|
||||
It is said that Go is the C language of the 21st century. I think there are two reasons: first, Go is a simple language; second, concurrency is a hot topic in today's world, and Go supports this feature at the language level.
|
||||
|
||||
## goroutine
|
||||
|
||||
goroutines and concurrency are built into the core design of Go. They're similar to threads but work differently. More than a dozen goroutines maybe only have 5 or 6 underlying threads. Go also gives you full support to sharing memory in your goroutines. One goroutine usually uses 4~5 KB of stack memory. Therefore, it's not hard to run thousands of goroutines on a single computer. A goroutine is more lightweight, more efficient and more convenient than system threads.
|
||||
|
||||
goroutines run on the thread manager at runtime in Go. We use the `go` keyword to create a new goroutine, which is a function at the underlying level ( ***main() is a goroutine*** ).
|
||||
|
||||
go hello(a, b, c)
|
||||
|
||||
Let's see an example.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
func say(s string) {
|
||||
for i := 0; i < 5; i++ {
|
||||
runtime.Gosched()
|
||||
fmt.Println(s)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go say("world") // create a new goroutine
|
||||
say("hello") // current goroutine
|
||||
}
|
||||
|
||||
Output:
|
||||
|
||||
hello
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
world
|
||||
hello
|
||||
|
||||
We see that it's very easy to use concurrency in Go by using the keyword `go`. In the above example, these two goroutines share some memory, but we would better off following the design recipe: Don't use shared data to communicate, use communication to share data.
|
||||
|
||||
runtime.Gosched() means let the CPU execute other goroutines, and come back at some point.
|
||||
|
||||
The scheduler only uses one thread to run all goroutines, which means it only implements concurrency. If you want to use more CPU cores in order to take advantage of parallel processing, you have to call runtime.GOMAXPROCS(n) to set the number of cores you want to use. If `n<1`, it changes nothing. This function may be removed in the future, see more details about parallel processing and concurrency in this [article](http://concur.rspace.googlecode.com/hg/talk/concur.html#landing-slide).
|
||||
|
||||
## channels
|
||||
|
||||
goroutines run in the same memory address space, so you have to maintain synchronization when you want to access shared memory. How do you communicate between different goroutines? Go uses a very good communication mechanism called `channel`. `channel` is like a two-way pipeline in Unix shells: use `channel` to send or receive data. The only data type that can be used in channels is the type `channel` and the keyword `chan`. Be aware that you have to use `make` to create a new `channel`.
|
||||
|
||||
ci := make(chan int)
|
||||
cs := make(chan string)
|
||||
cf := make(chan interface{})
|
||||
|
||||
channel uses the operator `<-` to send or receive data.
|
||||
|
||||
ch <- v // send v to channel ch.
|
||||
v := <-ch // receive data from ch, and assign to v
|
||||
|
||||
Let's see more examples.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func sum(a []int, c chan int) {
|
||||
total := 0
|
||||
for _, v := range a {
|
||||
total += v
|
||||
}
|
||||
c <- total // send total to c
|
||||
}
|
||||
|
||||
func main() {
|
||||
a := []int{7, 2, 8, -9, 4, 0}
|
||||
|
||||
c := make(chan int)
|
||||
go sum(a[:len(a)/2], c)
|
||||
go sum(a[len(a)/2:], c)
|
||||
x, y := <-c, <-c // receive from c
|
||||
|
||||
fmt.Println(x, y, x + y)
|
||||
}
|
||||
|
||||
Sending and receiving data in channels blocks by default, so it's much easier to use synchronous goroutines. What I mean by block is that a goroutine will not continue when receiving data from an empty channel, i.e (`value := <-ch`), until other goroutines send data to this channel. On the other hand, the goroutine will not continue until the data it sends to a channel, i.e (`ch<-5`), is received.
|
||||
|
||||
## Buffered channels
|
||||
|
||||
I introduced non-buffered channels above. Go also has buffered channels that can store more than a single element. For example, `ch := make(chan bool, 4)`, here we create a channel that can store 4 boolean elements. So in this channel, we are able to send 4 elements into it without blocking, but the goroutine will be blocked when you try to send a fifth element and no goroutine receives it.
|
||||
|
||||
ch := make(chan type, n)
|
||||
|
||||
n == 0 ! non-buffer(block)
|
||||
n > 0 ! buffer(non-block until n elements in the channel)
|
||||
|
||||
You can try the following code on your computer and change some values.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 2) // change 2 to 1 will have runtime error, but 3 is fine
|
||||
c <- 1
|
||||
c <- 2
|
||||
fmt.Println(<-c)
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
|
||||
## Range and Close
|
||||
|
||||
We can use range to operate on buffer channels as in slice and map.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func fibonacci(n int, c chan int) {
|
||||
x, y := 1, 1
|
||||
for i := 0; i < n; i++ {
|
||||
c <- x
|
||||
x, y = y, x + y
|
||||
}
|
||||
close(c)
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int, 10)
|
||||
go fibonacci(cap(c), c)
|
||||
for i := range c {
|
||||
fmt.Println(i)
|
||||
}
|
||||
}
|
||||
|
||||
`for i := range c` will not stop reading data from channel until the channel is closed. We use the keyword `close` to close the channel in above example. It's impossible to send or receive data on a closed channel; you can use `v, ok := <-ch` to test if a channel is closed. If `ok` returns false, it means the there is no data in that channel and it was closed.
|
||||
|
||||
Remember to always close channels in producers and not in consumers, or it's very easy to get into panic status.
|
||||
|
||||
Another thing you need to remember is that channels are not like files. You don't have to close them frequently unless you are sure the channel is completely useless, or you want to exit range loops.
|
||||
|
||||
## Select
|
||||
|
||||
In the above examples, we only use one channel, but how can we deal with more than one channel? Go has a keyword called `select` to listen to many channels.
|
||||
|
||||
`select` is blocking by default and it continues to execute only when one of channels has data to send or receive. If several channels are ready to use at the same time, select chooses which to execute randomly.
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
func fibonacci(c, quit chan int) {
|
||||
x, y := 1, 1
|
||||
for {
|
||||
select {
|
||||
case c <- x:
|
||||
x, y = y, x + y
|
||||
case <-quit:
|
||||
fmt.Println("quit")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
quit := make(chan int)
|
||||
go func() {
|
||||
for i := 0; i < 10; i++ {
|
||||
fmt.Println(<-c)
|
||||
}
|
||||
quit <- 0
|
||||
}()
|
||||
fibonacci(c, quit)
|
||||
}
|
||||
|
||||
`select` has a `default` case as well, just like `switch`. When all the channels are not ready for use, it executes the default case (it doesn't wait for the channel anymore).
|
||||
|
||||
select {
|
||||
case i := <-c:
|
||||
// use i
|
||||
default:
|
||||
// executes here when c is blocked
|
||||
}
|
||||
|
||||
## Timeout
|
||||
|
||||
Sometimes a goroutine becomes blocked. How can we avoid this to prevent the whole program from blocking? It's simple, we can set a timeout in the select.
|
||||
|
||||
func main() {
|
||||
c := make(chan int)
|
||||
o := make(chan bool)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case v := <- c:
|
||||
println(v)
|
||||
case <- time.After(5 * time.Second):
|
||||
println("timeout")
|
||||
o <- true
|
||||
break
|
||||
}
|
||||
}
|
||||
}()
|
||||
<- o
|
||||
}
|
||||
|
||||
## Runtime goroutine
|
||||
|
||||
The package `runtime` has some functions for dealing with goroutines.
|
||||
|
||||
- `runtime.Goexit()`
|
||||
|
||||
Exits the current goroutine, but defered functions will be executed as usual.
|
||||
|
||||
- `runtime.Gosched()`
|
||||
|
||||
Lets the scheduler execute other goroutines and comes back at some point.
|
||||
|
||||
- `runtime.NumCPU() int`
|
||||
|
||||
Returns the number of CPU cores
|
||||
|
||||
- `runtime.NumGoroutine() int`
|
||||
|
||||
Returns the number of goroutines
|
||||
|
||||
- `runtime.GOMAXPROCS(n int) int`
|
||||
|
||||
Sets how many CPU cores you want to use
|
||||
|
||||
## Links
|
||||
|
||||
- [Directory](preface.md)
|
||||
- Previous section: [interface](02.6.md)
|
||||
- Next section: [Summary](02.8.md)
|
||||
|
||||
Reference in New Issue
Block a user