0%

Go Concurrency programming Demo: Two Printer

引言

本文是并发编程的系列的最后一篇, 将通过Demo的方式, 尽可能的展示之前提到的技巧, 顺便做一个总结吧; 实现一个Demo: 双协程交替打印.

Demo

为何做这个Demo呢? 那是因为之前面试面到了, 但是很尴尬, 没写出来.

要求

给定一组数组, 要求使用两个协程交替打印数组;

代码

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
54
55
56
package main

import (
"context"
"fmt"
"sync"
)

func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
in := make(chan int)

ctx, cancel := context.WithCancel(context.Background())
defer cancel()

var wg sync.WaitGroup
wg.Add(2)

go func() {
defer wg.Done()
for _, n := range nums {
select {
case <-ctx.Done():
return
case in <- n:
}
}
close(in)
}()

output := func(name string, control <-chan struct{}, nextControl chan<- struct{}) {
defer wg.Done()
for {
select {
case <-ctx.Done():
return
case <-control:
n, ok := <-in
if !ok {
return
}
fmt.Printf("%s Goroutine get number: %v\n", name, n)
nextControl <- struct{}{}
}
}
}

controlOne := make(chan struct{})
controlTwo := make(chan struct{})

go output("One", controlOne, controlTwo)
go output("Two", controlTwo, controlOne)

controlOne <- struct{}{}
wg.Wait()
}

效果

1
2
3
4
5
6
7
8
9
10
11
[root@VM-0-9-centos learnGo]# go run ./cmd/main.go 
One Goroutine get number: 1
Two Goroutine get number: 2
One Goroutine get number: 3
Two Goroutine get number: 4
One Goroutine get number: 5
Two Goroutine get number: 6
One Goroutine get number: 7
Two Goroutine get number: 8
One Goroutine get number: 9
Two Goroutine get number: 10

思路

整体思路: 实现生产者与消费者模型, 有一个生产者, 负责将数组内的元素发送到消费通道内, 供消费者处理; 由于题目要求两个协程交替打印, 那么需要存在两个消费者依次消费;

  • 如何实现两个消费者依次消费: 两个消费者均采用同步通道控制, A消费完则告知B可以消费, B消费完则告知A可以消费;
  • 如何保证消费协程优雅退出: context
  • 如何保证主程序与子协程之间的同步: sync.WaitGroup

流程:

  1. 初始化变量和通道:在main函数中,初始化整数切片nums,创建一个整型通道in用于存放数字,以及使用context.Background()创建一个context.Context对象ctx和一个sync.WaitGroup对象wg
  2. **启动生产者goroutine**:启动一个匿名的goroutine,通过for range循环遍历nums切片,并向in通道发送数字。在发送完毕后,关闭in通道。select语句中判断ctx.Done(),若收到退出信号则退出循环。
  3. **启动消费者goroutine**:定义一个输出函数output,用于消费数字并输出。通过goroutine启动两个消费者,分别为”One”和”Two”。每个消费者通过select语句监听ctx.Done()和控制通道,接收到信号后输出数字并向下一个消费者发送信号。若通道关闭,则退出循环。
  4. 启动消费者流程:向controlOne通道发送一个信号,启动第一个消费者流程。等待所有goroutine执行完毕,使用wg.Wait()来等待。

通过使用context.Context实现优雅退出和sync.WaitGroup等待所有goroutine执行完毕