Blocking deadlock details

content

1. Channel blocking/deadlock example

Second, the deadlock solution

1. Method 1: Consume the channel first

2. Method 2: Use a buffer channel

3. In and out sequence of channel data

1. Unbuffered channel

2. Buffer channel

4. Waiting for multi-gorountine solutions


1. Channel blocking/deadlock example

Deadlock: All threads or processes are waiting for the release of resources

1) Deadlock Example 1

func main() {
    ch := make(chan int)
    <- ch // 阻塞main goroutine, 信道ch被锁
}

//执行这个程序你会看到Go报这样的错误:
fatal error: all goroutines are asleep - deadlock!

In the above program, there is only one goroutine, so when you add data or store data to it, the channel will be locked and the current goroutine will be blocked

2) Deadlock example 2

var ch1 chan int = make(chan int)
var ch2 chan int = make(chan int)

func say(s string) {
    fmt.Println(s)
    ch1 <- <- ch2 // ch1 等待 ch2流出的数据
}

func main() {
    go say("hello")
    <- ch1  // 堵塞主线
}

        Among them, the main line waits for the data in ch1 to flow out, and ch1 waits for the data in ch2 to flow out, but ch2 waits for the data to flow in, and both goroutines are waiting, that is, deadlock.

3) Deadlock summary

By default, the channel's save and fetch messages are blocking, that is, an unbuffered channel will suspend the current goroutine when fetching and saving messages, unless the other end is ready.

If there is no inflow and no outflow, or no inflow or outflow on a non-buffered channel, a deadlock will result. Or understand that the non-buffered channels in all goroutines started by Go must store data in one line and fetch data in one line, and they must be paired. Almost all unpaired channel access data will deadlock, but there are also special ones, as follows:

func main() {
    c := make(chan int)

    go func() {
       c <- 1
    }()
}

        The program exits normally. It's very simple. It's not that our summary doesn't work. Main didn't wait for other goroutines and finished running first, so no data flowed into the c channel. A total of one goroutine was executed, and there was no blocking, so no Deadlock error. Said to allow the channel to block the main line ( <- c )

Second, the deadlock solution

1. Method 1: Consume the channel first

package main

import (
    "fmt"
)

func f1(in chan int) {
    fmt.Println(<-in)
}

func main() {
    out := make(chan int)
    //调整下位置,先消费
    go f1(out)
    out <- 2
}

2. Method 2: Use a buffer channel

        Adding a buffer becomes a buffered channel. The buffered channel has capacity. If you store a piece of data, you can put it in the channel first, without blocking the current line and waiting for the data to be taken away. When the buffer channel reaches the full state, it will show blocking, because it can no longer carry more data at this time, "you must take the data away before you can flow in the data".

Buffered channel ch can stream 3 elements unbuffered

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3
}

        If you try to flow in another data, the channel ch will block the main line and report a deadlock. That is, the buffer channel will be locked when it is full.

3. In and out sequence of channel data

1. Unbuffered channel

var ch chan int = make(chan int)

func foo(id int) { //id: 这个routine的标号
    ch <- id
}

func main() {
    // 开启5个routine
    for i := 0; i < 5; i++ {
        go foo(i)
    }

    // 取出信道中的数据
    for i := 0; i < 5; i++ {
        fmt.Print(<- ch)
    }
}

The data of the unbuffered channel is first come first out, but the unbuffered channel does not store data, it is only responsible for the flow of data

2. Buffer channel

func main() {
    ch := make(chan int, 3)
    ch <- 1
    ch <- 2
    ch <- 3

    fmt.Println(<-ch) // 1
    fmt.Println(<-ch) // 2
    fmt.Println(<-ch) // 3
}

The buffer channel is also first in first out, we can think of the buffer channel as a thread-safe queue

4. Waiting for multi-gorountine solutions

Well, let's go back to the original problem, using the channel to block the main line, waiting for all the goroutines to run out.

This is a model that opens up many small goroutines, each of them runs their own, and finally reports to the main line when they are finished.

We discuss the following 2 versions of the scheme:

  1. Block the main line with only a single unbuffered channel
  2. Use a buffered channel with capacity equal to the number of goroutines

For scenario 1, the example code would look like this:

var quit chan int // 只开一个信道

func foo(id int) {
    fmt.Println(id)
    quit <- 0 // ok, finished
}

func main() {
    count := 1000
    quit = make(chan int) // 无缓冲
    
    for i := 0; i < count; i++ {
        go foo(i)
    }

    for i := 0; i < count; i++ {
        <- quit
    }
}

For option 2, change the channel to buffer 1000:

quit = make(chan int, count) // 容量1000

In fact, the only difference is that one is buffered and the other is non-buffered.

For this scenario, both can accomplish the task, both are ok.

  • An unbuffered channel is a batch of data "flowing in and out" one by one
  • The buffer channel is stored one by one and then streamed out together

Guess you like

Origin blog.csdn.net/demored/article/details/124147751