[Goroutine] Use multiple coroutines to print the alphabet in sequence concurrently


theme: channing-cyan

Today I will share a very classic concurrency problem, using multiple coroutines to print letters of the alphabet in order, each printing 10 times.

Idea: Obviously, our pipe and coroutine are required to complete synchronous and alternate printing. First, narrow the problem and think about the situation where three coroutines print a, b, and c. The most direct idea is to define three pipelines. After the first coroutine finishes printing, it notifies the next coroutine. After the last coroutine completes printing, it notifies the first coroutine to continue printing, thus forming a loop.

code show as below:

```go // Use three pipes to implement three coroutines to print synchronously and sequentially abc
func printLetter(letter string, prevCh, nextCh chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()

for i := 0; i < 10; i++ {  
    // 等待上一个协程通知  
    <-prevCh  
    fmt.Print(letter)  
    // 发送信号给下一个协程  
    nextCh <- struct{}{}  
}

}

func main() { var wg sync.WaitGroup
wg.Add(3)

ch1 := make(chan struct{})  
ch2 := make(chan struct{})  
ch3 := make(chan struct{})  

go printLetter("a", ch1, ch2, &wg)  
go printLetter("b", ch2, ch3, &wg)  
go printLetter("c", ch3, ch1, &wg)  

// 启动第一个协程  
ch1 <- struct{}{}  

wg.Wait()

} ```

Run the code and you will be surprised to find that the final result is printed, but a deadlock problem occurs. For programmers with technical pursuits, how can they just let it go? It must be solved for them.

image.png

Analysis of the problem: The root of the problem is that when we notify the next coroutine to print letters, a ring will eventually be formed. Then the first and second coroutines will exit after printing, and the last coroutine will exit after printing. Will pipe ch1 to do ch1 <- struct{}{}the operation. Because we define an unbuffered pipe , the third coroutine will block immediately, but the first coroutine has already exited and there is no way to operate on ch1 <-ch1, so the last coroutine will always block and WaitGroupthe counter cannot be set. The zero main coroutine cannot exit, eventually leading to a deadlock between the last coroutine and the main coroutine, and the program crashes.

解决方法也很简单,只要在 printLetter 函数中加一个判断,判断它是否是第一个协程,如果是那么就对 prevCh 做 <-prevCh 操作以避免死锁问题。

```go func printLetter(letter string, prevCh, nextCh chan struct{}, wg *sync.WaitGroup) {
defer wg.Done()

for i := 0; i < 10; i++ {  
    // 等待上一个协程通知  
    <-prevCh  
    fmt.Print(letter)  
    // 发送信号给下一个协程  
    nextCh <- struct{}{}  
}

if letter == "a" {
    <-prevCh
}

} ```

这样第 1 个协程必须得等最后一个协程做 nextCh <- struct{}{} 操作才能退出,或者说最后一个协程等待第 1 个协程做 <-prevCh 操作才能退出。最终主协程也可以安全地退出。

对于使用多协程顺序打印字母表的问题,相信你读到这里也有思路了吧,代码如下:

```go // 使用26个协程分别顺序打印字母表
func printAlphabet(letter rune, prevCh, nextCh chan struct{}, wg *sync.WaitGroup) {
defer wg.Done() for i := 0; i < 10; i++ {
<-prevCh fmt.Printf("%c", letter) nextCh <- struct{}{} }
// 第一个协程必须要退出,因为最后一个协程往管道里面写入数据了,需要破环而出不然就会死锁
if letter == 'a' { <-prevCh } }

func main() {
var wg sync.WaitGroup wg.Add(26)

var signals []chan struct{}  
for i := 0; i < 26; i++ {  
    signals = append(signals, make(chan struct{}))  
}  

for letter, i := 'a', 0; letter <= 'z'; letter++ {  
    if letter == 'z' {  
        go printAlphabet(letter, signals[i], signals[0], &wg)
        break
    }
    go printAlphabet(letter, signals[i], signals[i+1], &wg)  
    i++
}

// 启动第一个协程  
signals[0] <- struct{}{}  
wg.Wait()

} ```

这里我使用了一个切片存储了 26 个管道,这样避免了写重复代码。最终还是跟上面的代码一样,最后一个协程得要等第 1 个协程一起退出才不会死锁。

おすすめ

転載: blog.csdn.net/weixin_45254062/article/details/132059697