SoFunction
Updated on 2025-03-05

Some issues that need to be paid attention to when using golang channels

environment

  • Go 1.20
  • Windows 11

common sense

1. Define channel variables:

ch := make(chan int) // Int type data can be stored, the buffer is 0ch := make(chan any) // Can store any type of data, buffered to 0ch := make(chan int, 5) // Store int type data, buffering is 5// The default channel can be written and read, but we can also limit the direction of the channelch := make(<-chan int) // Only data can be read from this channel, and this channel cannot be closedch := make(chan<- int) // Only data can be written to this channellength := len(ch) // How many data is there in the channelcapacity := cap(ch) // The buffer size of the channel

2. Channels follow FIFO first-in, first-out rules, which can ensure the order of elements.

3. Channels are concurrently safe and will not cause data errors due to simultaneous writing of multiple coroutines.

Note

The following code example often calls the display function. This is a function I defined myself and is mainly used to print information. The code is as follows:

func display(msg ...any) {
    (().Format(), " ")
    (msg...)
}

In order to reduce code redundancy, the following code example will no longer post the code for this function.

1. When reading and writing to a channel that is not closed, if a blockage encounters and no other active (non-blocking) coroutines are running at this time, a deadlock error will be reported!

How to understand this sentence? First of all, you need to understand under what circumstances will block when reading and writing channels:

  • It blocks when writing data to a channel that is full of buffer
  • Reading empty channels will block
  • The channel is not initialized, for example, var ch chan int is not initialized

For point 1, assuming the channel buffer is N, it will block on N + 1st write (if the size of N is not specified when defining the channel variable, N is equal to 0 by default)

For point 2, if this empty channel is closed, it will not block, and the zero value of this channel data type is read.

Example 1:

func main() {
    ch := make(chan int)
    // Coroutine 1    go func() {
        for i := 0; i < 3; i++ {
            display("Prepare to send:", i)
            ch <- i
            display("Sent:", i)
        }
    }()
    for data := range ch {
        display("Get data:", data)
    }
}

The above code will report an error after running:fatal error: all goroutines are asleep - deadlock!

The reason is that after [Coeproc 1] writes 3 data into the channel, [Coeproc 1] ends running. At this time, after [main coroutine] (yes, the main function is also running in the coroutine) reads these 3 data, it does not exit the for-range loop, but continues to read the empty ch channel, blocking occurs, but at this time only [main coroutine] is running, and only one coroutine is left, so an error is reported.

Example 1:

func main() {
    ch := make(chan int)
    // Coroutine 1    go func() {
        for i := 0; i < 3; i++ {
            display("Prepare to send:", i)
            ch <- i
            display("Sent:", i)
        }
    }()
    // Coroutine 2    go func() {
        for data := range ch {
            display("Get data:", data)
        }
    }()
    // A dead loop    for {
    }
}

After the modified code, the reason is that after the [Coeproc 1] exits, although [Coeproc 2] is still blocking the empty channel, in addition to [Coeproc 2], there is also an active [main coroutine] running, so there will be no error.

Example 1 is modified again:

func main() {
    ch := make(chan int)
    // Coroutine 1    go func() {
        for i := 0; i < 3; i++ {
            display("Prepare to send:", i)
            ch <- i
            display("Sent:", i)
        }
        close(ch) // New code added    }()
    for data := range ch {
        display("Get data:", data)
    }
}

After coroutine 1 has written all data, it uses close(ch) to close the channel, and there will be no errors reported at this time. The reason is that for closed channels, after the for-range loop reads the channel data, the loop will automatically end, and will not block at the reading channel, so there will be no errors.

2. Send data to a closed channel, or close a closed channel again, which will cause panic

This sentence tells us that when the sender no longer needs to send data, the channel can be closed, but the receiver cannot be closed.
Because the receiver does not know whether the sender still needs to send data, if the channel is closed randomly, the sender will trigger the panic

3. The closed channel can continue to read the data inside.

func main() {
    ch := make(chan int, 2)
    ch <- 123
    ch <- 456
    close(ch)
    // Use for-range to read the closed channel. After the channel is empty, the loop will automatically jump out of the loop.    for data := range ch {
        display(data)
    }
    // Method 2: Use the ok variable to determine whether the channel is empty    /*for {
        data, ok := <-ch
        if !ok {
            break
        }
        display(data)
    }*/
    // Method 3: Use the channel length to determine whether the channel is empty    /*num := len(ch)
    for i := 0; i < num; i++ {
        data := <-ch
        display(data)
    }*/
}

4. Bidirectional channels can be passed to functions with parameters as unidirectional channels.

// Function parameters are unidirectional channelsfunc sendMessage(in chan<- int) {
    for i := 0; i < 3; i++ {
        in <- i
    }
    close(in)
}
func main() {
    ch := make(chan int) // Two-way channel    go sendMessage(ch)
    for data := range ch {
        display(data)
    }
}

5. When the read channel is used in conjunction with select and the timeout time is set, the channel must be buffered.

Let’s take a look at the example first:

func sendMessage(in chan<- int, sleep ) {
    (sleep)
    in <- 1
}
func main() {
    display("start")
    display("Number of coroutines:", ())
    ch1 := make(chan int) // mistake    // Correct: ch1 := make(chan int, 1)    // Coroutine 1    go sendMessage(ch1, 5 * )
    select {
    case v := <-ch1:
        display("The data was obtained from channel 1:", v)
    case <-(1 * ):
        display("Timeout, exit select")
    }
    for {
        display("Number of coroutines:", ())
        (1 * )
    }
}

As shown in the above code, at the beginning we created an unbuffered channel ch1, and then turned on [Coeproc 1]. [Coeproc 1] will write a data to the channel after 5 seconds, but the timeout time of select is only set for 1 second. In other words, before [Coecho 1] writes data to the channel, the select statement has ended due to timeout. At this time, there is no receiver in the ch1 channel, only the sender is left. Writing data to an unbuffered channel will cause [Coeisure 1] to block, and without the receiver, [Coeisure 1] will block forever and cannot end the exit, resulting in coroutine leakage.

Observe that the number of coroutines printed after the timeout is always 2 and will not be reduced to 1, which also confirms the above statement. Therefore, when defining channel variables, you must set a buffer.

In fact, increasing the timeout time of select can also solve this problem. But sometimes we may not know the specific execution time of the coroutine, so as to estimate a reasonable timeout time, so to be safe, it is better to define a buffered channel.

This is the article about some issues that need to be paid attention to when using golang channels. For more related golang channels, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!