本文仅是自己阅读笔记,不正确之处请多包涵和纠正。
原文The way to go
我们知道协程是独立执行的,他们之间没有通信。协程间必须通过通信协调/同步他们的工作。
协程可以使用共享变量来通信,但是在Go中并不提倡这样做,因为这种方式给所有的共享内存的多线程都带来了困难。
而Go是通过一种特殊的类型,通道(channel),一个可以用于发送类型化数据的管道,由其负责协程之间的通信,从而避开所有由共享内存导致的陷阱;这种通过通道进行通信的方式保证了同步性。数据在通道中进行传递:在任何给定时间,一个数据被设计为只有一个协程可以对其访问,所以不会发生数据竞争。 数据的所有权(可以读写数据的能力)也因此被传递。
声明:var identifier chan datatype
初始化: identifier = make(chan datatype)
or
identifier := make(chan datatype)
make()
函数来给它分配内存。interface{}
也可以。或者函数通道:funcChan := make(chan func())
,甚至可以(有时非常有用)创建通道的通道。ch <- int1
表示:用通道 ch 发送变量 int1int2 = <- ch
表示:变量 int2 从通道 ch接收数据,如果int2没有声明过,可以写成int2 := <- ch
<- ch
可以单独调用获取通道的(下一个)值,当前值会被丢弃,但是可以用来验证,例如:if <- ch != 1000{
...
}
package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan string)
go sendData(ch)
go getData(ch)
time.Sleep(1e9)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokyo"
}
func getData(ch chan string) {
var input string
// time.Sleep(2e9)
for {
input = <-ch
fmt.Printf("%s ", input)
}
}
输出:
Washington Tripoli London Beijing tokyo
示例解释:
all goroutines are asleep-deadlock!
。因为运行时(runtime)会检查所有的协程是否在等待着什么东西(可从某个通道读取或者写入某个通道),这意味着程序将无法继续执行。这是死锁(deadlock)的一种形式。**默认情况下,通信是同步且无缓冲的:在有接受者接收数据之前,发送不会结束。**一个无缓冲的通道在没有空间来保存数据的时候:必须要一个接收者准备好接收通道的数据然后发送者可以直接把数据发送给接收者。
所以通道的发送/接收操作在对方准备好之前是阻塞的:
示例:
package main
import (
"fmt"
"time"
)
func main() {
ch1 := make(chan int)
go pump(ch1)
fmt.Println(<-ch1)
// prints only 0
time.Sleep(1e9)
}
func pump(ch chan int) {
for i := 0; ; i++ {
ch <- i
fmt.Println("次数:", i)
}
}
输出:
次数: 0
0
我们可以看到接收了一次,后面并无接收者接收,所以在ch1通道里,发送操作是阻塞的。
当为通道解除阻塞定义了 suck 函数来在无限循环中读取通道:
func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}
在 main() 中使用协程开始它:
go pump(ch1)
go suck(ch1)
time.Sleep(1e9)
给程序 1 秒的时间来运行:输出了上万个整数。
通信是一种同步形式:通过通道,两个协程在通信中某刻同步交换数据。无缓冲通道成为了多个协程同步的完美工具。
甚至可以在通道两端互相阻塞对方,形成了叫做死锁的状态。Go 运行时会检查并 panic,停止程序。
go协程的死锁例子:
package main
import (
"fmt"
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
out <- 2
go f1(out)
}
Go 运行时检查并 panic: fatal error: all goroutines are asleep - deadlock!
(所有的协程都休眠了 - 死锁!)
死锁原因:在out无缓冲通道还没准备好接收的时候,在主线程中进行了发送操作,导致阻塞在这,无法执行到下一步以致所有的协程都进入休眠。
解决死锁办法1:将创建协程进行通道接收的操作放到发送操作之前
package main
import (
"fmt"
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int)
go f1(out)
out <- 2
}
输出:2
解决死锁办法2:将通道设置为缓冲通道,缓冲通道的内容请继续往下看
一个无缓冲通道只能包含 1 个元素,有时显得很局限。我们给通道提供了一个缓存,可以在扩展的 make 命令中设置它的容量,如下:
buf := 100
ch1 := make(chan string, buf)
所以上面使用缓冲通道解决死锁的例子:
package main
import (
"fmt"
"time"
)
func f1(in chan int) {
fmt.Println(<-in)
}
func main() {
out := make(chan int, 1)
out <- 2
go f1(out)
time.Sleep(1e7)
}
输出:2
为了知道计算何时完成,可以通过信道回报。在例子 go sum(bigArray) 中,要这样写:
package main
import "fmt"
func main() {
ch := make(chan int)
bigArray := []int{1,2,3,4}
go sum(bigArray, ch)
sum := <- ch
fmt.Println(sum)
}
func sum(bigArray []int, ch chan int) {
sum := 0
for _,v := range bigArray{
sum += v
}
ch <- sum
}
输出:10
也可以使用通道来达到同步的目的,这个很有效的用法在传统计算机中称为信号量。或者换个方式:通过通道发送信号告知处理已经完成(在协程中)。
在其他协程运行时让 main 程序无限阻塞的通常做法是在 main 函数的最后放置一个 select {}。
也可以使用通道让 main 程序等待协程完成,就是所谓的信号量模式,我们会在接下来的部分讨论。
以下示例中,for 循环的每一个迭代是并行完成的:
for i, v := range data {
go func (i int, v float64) {
doSomething(i, v)
...
} (i, v)
}
在 for 循环中并行计算迭代可能带来很好的性能提升。不过所有的迭代都必须是独立完成的。
for 循环的 range 语句可以用在通道 ch 上,便可以从通道中获取值,像这样:
for v := range ch {
fmt.Printf("The value is %v\n", v)
}
它从指定通道中读取数据直到通道关闭,才继续执行下边的代码。很明显,另外一个协程必须写入 ch(不然代码就阻塞在 for 循环了),而且必须在写入完成后才关闭。
上面通道阻塞的例子可以修改成如下:
package main
import (
"fmt"
"time"
)
func main() {
suck(pump())
time.Sleep(1e9)
}
func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}
func suck(ch chan int) {
go func() {
for v := range ch {
fmt.Println(v)
}
}()
}
通道类型可以用注解来表示它只发送或者只接收:
var send_only chan<- int // 只能往通道里发送数据
var recv_only <-chan int // 只能接受通道的数据
等待被接收数据的通道是无法关闭的,因为关闭通道是发送者用来表示不再给通道发送值了,所以对等待被接收通道是没有意义的。
通道创建的时候都是双向的,但也可以分配有方向的通道变量:
var c = make(chan int) // bidirectional
go source(c)
go sink(c)
func source(ch chan<- int){
for { ch <- 1 }
}
func sink(ch <-chan int) {
for { <-ch }
最后,说说channel的几个需要注意的: