goroutine
goroutine
是由go runtime
所管理的轻量级线程。
go f(x,y,z)
开启一个运行f(x,y,z)
的新goroutine。f
,x
,y
,z
值的计算发生在当前goroutine
,但f的执行则在新的goroutine
中。
goroutine
运行在同一地址空间中,所以共享内在的存取必须同步,sync
包提供一些有用的基本工具,但一般你用不到它们,因为还有其它的工具。
channel
channel
是一种有类型的管道,使用<-
操作符就可以通过它发送和接收数值。
1 2 3
| ch <- v // Send v to channel ch. v := <-ch // Receive from ch, and // assign value to v.
|
参数按箭头的方向流动。和map
及slice
一样,channel
也需要先定义再使用。
样例代码将slice
中数累加,将工作分布在两个goroutine
中,当二者完成时,得到最终结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| package main import "fmt" func sum(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum // send sum to c } func main() { s := []int{7, 3, 8, -9, 4, 0} c := make(chan int) go sum(s[:len(s)/2], c) go sum(s[len(s)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x+y) }
|
运行结果:
如果在两个goroutine
之间加上time.Sleep(1*time.Second)
,可以看到channel
是先进先出的,它是个队列。
buffered channel
channel
可以被缓存,在make
的时候设定第二个int
型参数就可以新建一个指定大小的buffered channel
.
1
| ch := make(chan int,100)
|
只有当buffer
已满时发送数据给channel
才会阻塞。接收数据则会在buffer
为空时阻塞。
range 和 close
发送者可以在无后续数据需要发送时关闭channel
,接收者可以在接收表达式后指定第二参数来测试channel
是否已被关闭。
当无数据可接收且channel已关闭时,ok为false.
for i := range c
循环不断地从channel
中接收数据直到其关闭。
只有发送者能关闭channel
,向已关闭的channel
发送数据会导致panic
。
channel
和文件不同,没有必要关闭它,除非接收者需要知道,比如结束for i := range c
这样的循环。
select
select
语句让goroutine
在多个通信操作上等待。select
块在它的任意一个case
能运行之前一直阻塞,如果多个case
就绪,则任选一个。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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) }
|
main
函数中的匿名goroutine
函数一开始是阻塞的,因为channel
中没有数据,fibonacci
开始运行之后,不断往c中写数据,匿名goroutine
开始循环打印数据,直到10次之后不再读取,此时fibonacci
中的第一个case
阻塞,运行第二个case
,结束进程。
default selection
default case会在其它case都不满足时运行。使用default关键字可实现无阻的发送/读取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| package main import ( "fmt" "time" ) func main() { tick := time.Tick(100 * time.Millisecond) boom := time.After(500 * time.Millisecond) for { select { case <-tick: fmt.Println("tick.") case <-boom: fmt.Println("BOOM!") return default: fmt.Println(" .") time.Sleep(50 * time.Millisecond) } } }
|
tick会周期地产生信号,而After会定时产生一次信号。在没有信号的时候,就会运行default
中的打印.
语句。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| . . tick. . . tick. . . tick. . . tick. . . tick. BOOM!
|
练习:等效二进制树
可能有多个二进制树储存了同样的值序列,例如以下两个二进制树就存储了相同的序列1,1,2,3,5,8,13。
检测两个树存储相同序列的函数通常非常复杂,我们将使用go
并发和channel
来写一个简单实现。
例子将使用tree
包,其中Tree
定义如下:
1 2 3 4 5
| type Tree struct { Left *Tree Value int Right *Tree }
|
1,实现walk
2,测试walk
tree.new(k)
将产生一个随机结构的二进制树,存储数值k,2k,3k,…10k
创建一个新channel ch
开始walker
:
1
| go walk( tree.New(k),ch)
|
然后从中读出10个化值,应该是1,2,3…10
3,使用walk
实现Same
函数,确定t1
,t2
存储相同序列。
4,测试same
。same(tree.New(1),tree.New(1))
返回真,same(tree.New(1),tree.New(2))
返回假。
以下是我的答案,不是很简练和健壮,但能得到预期结果。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| package main import "golang.org/x/tour/tree" import "fmt" import "time" import "sort" // Walk walks the tree t sending all values // from the tree to+ the channel ch. func Walk(t *tree.Tree, ch chan int) { ch <- t.Value if t.Left != nil { Walk(t.Left, ch) } if t.Right != nil { Walk(t.Right, ch) } } // Same determines whether the trees // t1 and t2 contain the same values. func Same(t1, t2 *tree.Tree) bool { ch1 := make(chan int, 10) go Walk(t1, ch1) ch2 := make(chan int, 10) go Walk(t2, ch2) time.Sleep(1 * time.Second) seq1 := make([]int, 10) seq2 := make([]int, 10) for i := 0; i < 10; i++ { x, y := <-ch1, <-ch2 seq1[i] = x seq2[i] = y } sort.Ints(seq1) sort.Ints(seq2) for i, v := range seq1 { if v != seq2[i] { return false } } return true } func main() { ok1 := Same(tree.New(1), tree.New(1)) ok2 := Same(tree.New(1), tree.New(2)) ok3 := Same(tree.New(2), tree.New(1)) fmt.Println(ok1, ok2, ok3) }
|