Home  >  Article  >  Backend Development  >  Understanding golang blocking channel behavior by writing multiple times to the channel

Understanding golang blocking channel behavior by writing multiple times to the channel

WBOY
WBOYforward
2024-02-09 14:27:20755browse

通过对通道进行多次写入来理解 golang 阻塞通道行为

In this article, editor Youzi will introduce to you how to understand the behavior of golang blocking the channel by writing multiple times to the channel. In golang, channels are an important mechanism for passing data between coroutines. When a channel is full, write operations are blocked until the channel becomes free. We'll demonstrate this behavior with a simple example and explain how blocking channels work and how to use them. Whether you are a beginner or an experienced golang developer, you can gain useful knowledge and practical experience from this article. let's start!

Question content

I am new to golang and trying to understand concurrency in the language. I have a code that pushes some values ​​to a channel and then reads them.

package main

import (
    "log"
    "time"
)

func Greet2(c chan string) {
    // logging to Stdout is not an atomic operation
    // so artificially, sleep for some time
    time.Sleep(2 * time.Second)
    
    // 5. until below line reads and unblock the channel
    log.Printf("5. Read Greet2:: %s\n\n", <-c)
}

func Greet(c chan string) {
    // 4. Push a new value to the channel, this will block
    // Process will look for other go routines to execute
    log.Printf("4. Add 'Greet::John' to the channel, block until it is read. Remember, 'Greet' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.\n\n")
    c <- "Greet::John!"
    
    // 8. This statement will never execute
    log.Printf("8. Read Greet:: %s !\n\n", <-c)
}

func main() {

    c := make(chan string)

    log.Println("1. Main start")
    
    // 2. Both go routine will be declared and both will
    // for a value to be inserted in the channel
    log.Println("2. Declare go routines.\n\n")
    go Greet(c)
    go Greet2(c)
    
    // 3. write will block
    log.Println("3. Add 'main::Hello' to the channel, block until it is read. Remember, 'main' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.\n\n")
    c <- "main::Hello"
    
    // Sleep to give time goroutines to execute
    time.Sleep(time.Second)
    
    // 6. read the channel value.
    log.Printf("6. Read main:: %s \n\n", <-c)
    
    // 7. Insert a new value to the channel
    log.Println("7. Add 'main::Bye' to the channel, block until it is read.\n")
    c <- "main::Bye"
    
    // Sleep to give time goroutines to execute
    time.Sleep(time.Second)
    log.Println("9. Main stop")

}

The output of the above program is

2023/09/02 21:58:07 1. Main start
2023/09/02 21:58:07 2. Declare go routines.


2023/09/02 21:58:07 3. Add 'main::Hello' to the channel, block until it is read. Remember, 'main' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.


2023/09/02 21:58:07 4. Add 'Greet::John' to the channel, block until it is read. Remember, 'Greet' goroutine will block and only other goroutines can run even though this go routine can pull the value out from the channel.

2023/09/02 21:58:10 5. Read Greet2:: main::Hello

2023/09/02 21:58:11 6. Read main:: Greet::John!

2023/09/02 21:58:11 7. Add 'main::Bye' to the channel, block until it is read.

2023/09/02 21:58:11 8. Read Greet:: main::Bye !

2023/09/02 21:58:12 9. Main stop

I can't understand why 4. (another write to channel) is executed before 5. (first read from channel) because 3. will block and the channel will be unavailable until the value is read from it (in step 5.). Did I misunderstand the blocking behavior, in step 3. there is only the main goroutine block and Greet (in step 4.) Can additional values ​​be written to a channel? An explanation would really clear up my confusion :)

cheers, D.D.

Thanks for your reply, I've created a simpler program to demonstrate. concurrent

package main

import (
        "fmt"
)

func do2(c chan int) {
        fmt.Println(<-c)
}

func do(c chan int) {
        // 4. this statement is trying to write another value "2" to the channel
        // Channel already contains "1" as the value which has not been read yet.
        // this statement will wait for "1" to get read and block the execution.
        // Scheduler will look for other goroutines that can execute.
        // However, this("do") is blocked as well as "main" is blocked too and
        // there are no other goroutines to execute.
        // Hence, will result in a "Deadlock" fatal error.
        c <- 2
        fmt.Println(<-c)
}

func main() {

        // 1. Declare a channel
        c := make(chan int)
        // 2. Declare "do" goroutine
        go do(c)
        // 3. write "1" to the channel
        // This will block and wait for program's other goroutines to read the value.
        // however, there is only "do" goroutine is defined can run at this point.
        // Scheduler, will try to run "do" goroutine.
        c <- 1
        go do2(c)

}

The deadlock can be fixed by exchanging the c <- 1 and go do2(c) statements.

Workaround

In Go, when you send a value on a channel (c <- "main::Hello" in step 3), send The Goroutine will block until another Goroutine is ready to receive a value from the channel. However, this does not mean that no other goroutines can continue executing. In your code, both the Greet and Greet2 coroutines are waiting for the value from the channel, so when you send the value in step 3, one of them (no guarantee which one) ) will unblock and continue execution.

Let me break down the sequence of events step by step:

  1. The main program starts and you create a channel c.
  2. You declare two goroutines, Greet and Greet2, and both are waiting for a value from the channel.
  3. You send a value "main::Hello" on the channel, which blocks the main goroutine until other goroutines read data from the channel. However, one of the two Goroutines (Greet or Greet2) is not blocked from receiving the value.
  4. Greet Unblock and continue execution. It logs the message "4. Add 'Greet::John' to channel..." and sends "Greet::John!" on the channel. This blocks Greet again because no other goroutine can read from the channel at this time.
  5. Greet2 Unblock and continue execution. It logs the message "5. Read Greet2::main::Hello" and reads the value "main::Hello" from the channel.
  6. Main unlocks, records "6. Read main::Greet::John!" and reads "Greetings::John!" from the channel.
  7. Main sends another value "main::Bye" on the channel. At this point, Greet is still blocked while writing to the channel, and Greet2 is blocked from not reading from the channel.
  8. Since Greet is still blocked on write, it never logs "8. Read Greet::main::Bye !"
  9. Main site.

So the key to understanding the behavior here is that when you send a value on a channel, it unlocks any goroutines that are waiting to read data from the channel. The order in which waiting goroutines are unblocked is undefined and depends on the scheduler. In your case, Greet2 happens to be unlocked first, but it could also be Greet.

In summary, the behavior you observe is entirely consistent with how Go channels work, with the caveat that execution order among competing Goroutines is not guaranteed.

The above is the detailed content of Understanding golang blocking channel behavior by writing multiple times to the channel. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:stackoverflow.com. If there is any infringement, please contact admin@php.cn delete