Contents
  1. 1. goroutine
  2. 2. channel
    1. 2.1. buffered channel
    2. 2.2. range 和 close
    3. 2.3. select
    4. 2.4. default selection
  3. 3. 练习:等效二进制树

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.

参数按箭头的方向流动。和mapslice一样,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)
}

运行结果:

1
-5 18 13

如果在两个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是否已被关闭。

1
v, ok := <- c

当无数据可接收且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,测试samesame(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)
}

Contents
  1. 1. goroutine
  2. 2. channel
    1. 2.1. buffered channel
    2. 2.2. range 和 close
    3. 2.3. select
    4. 2.4. default selection
  3. 3. 练习:等效二进制树