Concurrency
Goroutines
goroutine 是由 Go 运行时管理的轻量级线程。
go f(x, y, z)会启动一个新的 goroutine 来运行
Channel
定义
通道(channel)是带类型的管道,负责在多个 goroutine 之间传递数据并进行同步。
不要通过共享内存来通信,而要通过通信来共享内存。
不要让多个 goroutine 随意共同修改数据;通过 channel 明确数据由谁处理、何时交接。
创建
通道在使用前必须创建:ch := make(chan int)
读写
通过通道操作符 <- 发送和接收值。
ch <- v // Send v to channel ch.
v := <-ch // Receive from ch, and
// assign value to v.
Buffered Channels
通道可以是 带缓冲的。
ch := make(chan int, 100)
对于管道而言,
make函数接收两个参数,第一个是管道的类型,第二个是可选参数为管道的缓冲大小。向带缓冲的通道发送仅在缓冲区已满时才会阻塞。接收在缓冲区为空时阻塞。
Range and Close
-
发送者可以
close()一个通道,以表示不会再发送更多值。close的作用不是“释放资源”,而是通知接收方这个 channel 以后不会再有新值了。 -
接收者可以通过为接收表达式赋第二个参数来测试通道是否已关闭
在
v, ok := <-ch之后,如果没有更多值可接收且通道已关闭,则ok为false。
循环 for i := range c 会反复从通道接收值,直到它被关闭。
**注意:只有发送者应关闭通道,永远不要由接收者关闭。**向已关闭的通道发送会导致 panic。
只有接收者需要知道“数据已经发送完毕”时,发送者才需要关闭 channel;
普通的一次发送、一次接收通常不用关闭。
Select
select 语句让一个 goroutine 可以等待多个通信操作。
select 会阻塞,直到某个 case 可以运行,然后执行该 case。如果多个 case 都已就绪,它会随机选择一个。
package main
import "fmt"
func fibonacci(c, quit chan int) {
x, y := 0, 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)
}
如果没有其他 case 就绪,select 中的 default case 就会运行。
select {
case i := <-c:
// use i
default:
// receiving from c would block
}
sync.Mutex
互斥(mutual exclusion):同一时刻只允许一个 goroutine 访问共享数据,其他 goroutine 必须等待,从而避免并发读写冲突。
提供该能力的数据结构的惯用名称是 互斥锁(mutex)。
Go 的标准库通过 sync.Mutex 及其两个方法提供互斥:
LockUnlock
package main
import (
"fmt"
"sync"
)
var (
count int
mu sync.Mutex
)
func inc() {
mu.Lock()
defer mu.Unlock()
count++
}
func main() {
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1) // 待完成的 goroutine 数量加 1
go func() {
// 当前 goroutine 结束时,将待完成数量减 1
defer wg.Done()
inc()
}()
}
wg.Wait() // 等待计数减到 0,即所有 goroutine 执行完成
fmt.Println(count)
}