交替打印
问题描述:使用两个 goroutine
交替打印序列,一个 goroutine
打印数字,另外一个 goroutine
打印字母,最终效果如下:
12AB34CD56EF78GH910IJ1112KL1314MN1516OP1718QR1920ST2122UV2324WX2526YZ2728
这里主要考察线程间的通信方式,在 Go 语言中可使用 channel 传递消息来控制打印的进度,或使用其他特殊方式实现(如转成单线程程序)。
方法一:3个 channel
可以使用三个 channel 来实现,其中:
- 1 个
letterChan
通知打印字母的 goroutine
- 1 个
numberChan
通知打印数字的 goroutine
- 1
done
channel 接受终止信号
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
52
53
|
func main() {
// 3 个 channel
numberChan := make(chan bool) // letterChan 通知打印字母的 goroutine
letterChan := make(chan bool) // numberChan 通知打印数字的 goroutine
done := make(chan bool) // done 接受终止信号
// print number
go func() {
x := 1
for {
select {
case <-numberChan:
fmt.Print(x)
x++
fmt.Print(x)
x++
letterChan <- true
}
}
}()
// print letter
go func() {
c := 'A'
for {
select {
case <-letterChan:
// 判断停止
if c > 'Z' {
done <- true
return
}
fmt.Print(string(c))
c++
fmt.Print(string(c))
c++
numberChan <- true
}
}
}()
// 向一端发信号 开始打印
numberChan <- true
// letterChan <- true
// 主线程阻塞等待
for {
select {
case <-done:
return
}
}
}
|
方法二:2个 channel+wait
控制 goroutine 结束除了使用 channel 还可以使用 sync.wait
来实现
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
52
|
func main() {
// 两个 channel 负责通知
numberChan := make(chan bool) // letterChan 通知打印字母的 goroutine
letterChan := make(chan bool) // numberChan 通知打印数字的 goroutine
// wait 用来等待字母打印完成后退出循环
wait := sync.WaitGroup{}
// print number
go func() {
x := 1
for {
select {
case <-numberChan:
fmt.Print(x)
x++
fmt.Print(x)
x++
letterChan <- true
}
}
}()
wait.Add(1)
// print letter
go func() {
c := 'A'
for {
select {
case <-letterChan:
// 判断停止
if c > 'Z' {
wait.Done()
return
}
fmt.Print(string(c))
c++
fmt.Print(string(c))
c++
numberChan <- true
}
}
}()
// 向一端发信号 开始打印
numberChan <- true
// letterChan <- true
// 主线程阻塞等待
wait.Wait()
}
|
方法三:Gosched
通过 runtime.GOMAXPROCS(1)
设置 P 的数量为 1,保证只有一个线程执行任务。每次打印完主动调用 runtime.Gosched()
切换 goroutine 执行。
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
|
func main() {
runtime.GOMAXPROCS(1)
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
c := 'A'
for c <= 'Z' {
fmt.Print(string(c))
c++
fmt.Print(string(c))
c++
runtime.Gosched() // 挂起当前 goroutine
}
}()
go func() {
defer wg.Done()
i := 0
for i <= 26 {
fmt.Print(i)
i++
fmt.Print(i)
i++
runtime.Gosched() // 挂起当前 goroutine
}
}()
wg.Wait()
}
|
注意点
- 方法一和二都只退出了
letter goroutine
,number goroutine
在 main 函数结束后自动结束
- 方法三将程序变成了单线程,
runtime.Gosched()
可使用 time.Sleep(100 * time.Microsecond)
替代,但 sleep 的时间不易控制,不推荐。
- 方法一和二通过选择初始信号发给哪个 channel 来控制打印的顺序,方法三实测可以改变
go func()
代码顺序来切换(不确定是否一定正确,原理还未理清,最好想一个更优雅的切换方式)
Reference