0. CSP--Communicating Sequential Process
Don't communicate by sharing memory; share memory by communicating.
虽然可以使用共享内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。为了保证数据交换的正确性,必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。Go语言CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。
Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
1. go协程(go routine)
go原生支持并发:goroutine和channel。
go协程是与其他函数或方法一起并发运行的函数和方法。go协程可以看作是轻量级线程。
func runtime.GOMAXPROCS(n) int -- sets the maximum number of CPUs that can be executing simultaneously and returns the previous setting.
调用函数或者方法时,在前面加上关键字go,可以让一个新的GO协程并发地运行。
- l 启动一个新的协程时,协程的调用会立即返回。与函数不同,程序控制不会去等待 Go 协程执行完毕。在调用 Go 协程之后,程序控制会立即返回到代码的下一行,忽略该协程的任何返回值。
- l 如果希望运行其他 Go 协程,Go 主协程必须继续运行着。如果 Go 主协程终止,则程序终止,于是其他 Go 协程也不会继续运行。
常见的问题:go协程引用外部循环变量(参考:协程引用循环变量的问题)
//mu := &sync.Mutex for i := 0; i < 10; i++ { go func() { // mu.Lock() // mu.Unlock() fmt.Println(i) } () }
理想状态时输出1到10,无论是否有mutex,都不可能输出正确结果。大部分输出的数字是10,原因:
主协程的循环很快就跑完了,而各个协程才开始跑,此时i的值已经是10了,所以各协程都输出了10。(输出7或其他数字的协程,在开始输出的时候主协程的i值刚好是7,这个结果每次运行输出都不一样)
如果通过go vet main.go会报如下警告: main.go:24:16: loop variable i captured by func literal
for i := 0; i < 10; i++ { go func(i0 int) { fmt.Println(i0) } (i) // }
变量i已经有了一个副本,协程中针对副本处理。
使用goroutine原则:(管理goroutine生命周期)
1)goroutine什么时候终止;2)怎样终止goroutine。
2. 无缓冲信道channel
信道可以想象成Go协程之间通信的管道。
chan T 表示 T 类型的信道。信道与类型相关,只能运输该类型的数据。
信道是一种引用类型,信道的零值为 nil。信道的零值没有什么用,应该像对 map 和切片所做的那样,用 make 来定义信道或初始化信道。
var ch chan int // 声明信道
fmt.Println(ch) // <nil>
ch = make(chan int) //初始化
fmt.Println(ch) // 0xc000020180
data := <- a // 读取信道 a a <- data // 写入信道 a
无缓冲信道发送与接收默认是阻塞的,即无缓冲信道是同步的。
信道会产生死锁。当Go协程给一个信道发送数据,而没有信道接收数据时,程序触犯panic,形成死锁。
信道数据都是单传递的,当一个接收信道收到数据后,其他信道接收者就不能接收到相同数据,即信道数据只能由任意一个且仅有一个信道接收者获取到。
对一个nil信道发送或者接收数据都会造成永远阻塞。例如没有make初始化或定义的信道。
单向信道
sendch := make(chan<- int) // 定义单向信道,定义只写数据的信道,<-指向chan
只写通道:chan<- T
只读通道:<-chan T
有方向的channel不能被关闭。
可以把一个双向信道转换成唯送信道或者唯收信道(send only or receive only),但反过来不可以。
package main import ( "fmt" "time" "os" ) func main(){ data := make(chan int) go func(out chan<- int){ time.Sleep(2* time.Second) out <- 1 }(data) <- data fmt.Println("Receive data, first") go func(out <-chan int){ time.Sleep(2 * time.Second) <-out fmt.Println("Receive data, Second") os.Exit(0) }(data) data <- 2 for { time.Sleep(1 * time.Second) } }