I am new in golang and trying to understand the concurrency in the language. I have a code which pushes a few values to the 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 am unable to understand why 4.
(another write to the channel) is getting executed before 5.
(First read from the channel) as 3.
will block and channel would not be available until the value is read from it(in step 5.
). Am I misunderstanding the blocking behaviour, In step 3.
only main
goroutine blocks and Greet
(in step 4.
) can write additional value to the channel? An explanation would really resolve my confusion :)
Cheers, DD.
May thanks to the replies and I have created a simpler program to demo. the concurrency
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 swapping c <- 1
and go do2(c)
statements.
In Go, when you send a value on a channel (c <- "main::Hello"
in step 3), the sending goroutine will block until there is another goroutine ready to receive the value from the channel. However, this doesn't mean that no other goroutines can proceed. In your code, both Greet
and Greet2
goroutines are waiting for values from the channel, so when you send the value in step 3, one of them (it's not guaranteed which one) will unblock and proceed to execute.
Let me break down the sequence of events step by step:
c
.Greet
and Greet2
, and both are waiting for values from the channel.Greet
or Greet2
) is unblocked to receive this value.Greet
unblocks and proceeds to execute. It logs the message "4. Add 'Greet::John' to the channel..." and sends "Greet::John!" on the channel. This blocks Greet
again since there's no other goroutine to read from the channel at that moment.Greet2
unblocks and proceeds to execute. It logs the message "5. Read Greet2:: main::Hello" and reads the value "main::Hello" from the channel.Greet
is still blocked on writing to the channel, and Greet2
is blocked because it's not reading from the channel.Greet
is still blocked on writing, it never gets to log "8. Read Greet:: main::Bye !"So, the key to understanding the behavior here is that when you send a value on a channel, it unblocks any goroutine that is waiting to read from the channel. The order in which the waiting goroutines get unblocked is not determined and depends on the scheduler. In your case, Greet2
happened to get unblocked first, but it could have been Greet
as well.
In summary, the behavior you are observing is entirely consistent with how Go's channels work, and it's important to note that the order of execution between competing goroutines is not guaranteed.