I'm writing some golang concurrency codes with goroutines and channels
here's my codes:
package main
import "fmt"
func main() {
in := make(chan int)
go func() {
fmt.Println("Adding num to channel")
in <- 1
fmt.Println("Done")
}()
val := <- in
fmt.Println(val)
}
I make an unbuffered channel, in my opinion, The channel inside must wait until the channel outside reads it,and the output may like this :
Adding num to channel
1
Done
But in fact, the output is :
Adding num to channel
Done
1
I'm so confused that why the inside unbuffered channel just run without waiting for reading
Your interpretation of the output is incorrect. The goroutine did write to the channel, at which point the main goroutine did read, however the printf for "Done" executed before the printf for the value.
Synchronization operations establish a "happened before" relationship between goroutines. When the goroutine wrote the channel, the only thing that is guaranteed to happen before the channel write is the first println in the goroutine. Once the channel write and the corresponding read is done, the remaining of the goroutine and the main goroutine can execute in any order.
In your case, the goroutine executed before the main goroutine.
Related
I've boiled my issue down to this simple example below. I am invoking a goroutine that takes two channels and sends one message to each. Then I am attempting to receive those messages further along. However, the order of channels receiving matters. If I use the same order I sent the messages, the program runs. If I switch, it does not.
I would have expected the goroutine to run independently from retrieving the messages, allowing me to receive from whichever channel I wanted to first.
I can solve this by sending messages to a single channel per goroutine (2 goroutines).
Could someone explain why there is an order dependence here and why 2 separate goroutines resolves that dependence?
package main
import "fmt"
func main() {
chanA := make(chan string)
chanB := make(chan string)
go func() {
chanA <- "el"
chanB <- "el"
}()
// if B is received before A, fatal error
// if A is received before B, completes
<-chanB
<-chanA
fmt.Println("complete")
}
You will need to buffer your channels. A buffered channel can store so many elements before it will block.
chanA := make(chan string, 1)
chanA <- "el" // This will not block
fmt.Println("Hello World")
When you do chanA <- "el" on the buffered channel above, the element gets placed into the buffer and the thread does not block. If you add a second element, it will then block as there is no room in the buffer:
chanA := make(chan string, 1)
chanA <- "el"
chanA <- "el" // <- This will block, as the buffer is full
In your example, you have a buffer of 0. So the first write to the channel is blocked, and requires another thread to read the value to unblock.
https://go.dev/play/p/6GbsVW4d0Mg
chanA := make(chan string)
go func() {
time.Sleep(time.Second)
fmt.Println("Pop:", <-chanA) // Unblock the writer
}()
chanA <- "el"
Extra knowledge
If you do not want a thread to block, you can wrap a channel insert in a select. This will ensure if the channel is full, your application does not deadlock. One cheap way of fixing this is a larger buffer...
https://go.dev/play/p/kKR-lrCO4FX
select {
case chanA <- "el":
default:
return fmt.Errorf("value not written: %s", value)
}
this is how goroutine works:
a goroutine will be blocked read/write on a channel unless if find another goroutine which write/read from the same channel.
Pay attention to read/write and write/read in the above blocked quote.
In your case, your anon goroutine(which you kicked off with go) waits to write on channelA until it finds a goroutine which reads from channelA.
The main goroutine waits to read from channelB unless it finds a goroutine that reads from it.
You can think it this way, any line written after read/write to channel won't be considered unless go finds another routine which write/read from the same channel.
So, if you change either read or write order you will not have deadlock or as you said another goroutine will do the job too.
Hope it's clear.
A write to or read from an unbuffered channel will block until there is a goroutine to write to or read from that channel. When the goroutine writes to a, it will block until the main goroutine can read from a, but main goroutine is also blocked waiting to read from b, hence deadlock.
I'm currently following the tour of go tutorial and got to the section on channels, as I was doing some testing I found an weird behavior I'm struggling to understand
the following code produces a deadlock error
package main
import "fmt"
func main() {
c := make(chan string)
c <- "test"
fmt.Printf("%v", <- c)
}
but doing one of the following things fixes the code
using a buffered channel:
package main
import "fmt"
func main() {
c := make(chan string, 1)
c <- "test"
fmt.Printf("%v", <- c)
}
or setting the value to the channel on a different thread
package main
import "fmt"
func main() {
c := make(chan string)
go func(){c <- "test"}()
fmt.Printf("%v", <- c)
}
what is the underlying reason for the first version of the code to produce a deadlock?
Writing to an unbuffered channel will only succeed if there is another goroutine that reads from that channel. In the first case, you have only one goroutine, the main goroutine, that writes to an unbuffered channel, and there is no other goroutine that can read from it, so it is a deadlock.
The second one works, because the channel is buffered, and writing succeeds by filling the buffer. A second write without a read will deadlock.
The third one works, because write happens in a separate goroutine, which waits until the read in the first goroutine runs.
Since the channel has no buffer, c <- "test" will block until something reads from c. Since the reader comes after the write it will never reach the read and deadlock.
If the channel has a buffer, c <- "test" writes to the buffer and does not have to wait for a reader. The reader then reads from the channel buffer.
This is all because the reader and writer are in the same goroutine and so must execute one statement after the other. If the reader and writer were in different goroutines, the writer goroutine can block until the reader goroutine reads. Because of this, buffers are often unnecessary.
I am trying to understand channels in Go. I have read that by default sends and receives block until both the sender and receiver are ready. But how do we figure out readyness of sender and receiver.
For example in the following code
package main
import "fmt"
func main() {
ch := make(chan int)
ch <- 1
fmt.Println(<-ch)
}
The program will get stuck on the channel send operation waiting forever for someone to read the value. Even though we have a receive operation in println statement it ends up in a deadlock.
But for the following program
package main
import "fmt"
func main() {
ch := make(chan int)
go func () {
ch <- 1
}()
fmt.Println(<-ch)
}
The integer is passed successfully from go routine to main program. What made this program work? Why second works but first do not? Is go routine causing some difference?
Let's step through the first program:
// My notes here
ch := make(chan int) // make a new int channel
ch <- 1 // block until we can send to that channel
// keep blocking
// keep blocking
// still waiting for a receiver
// no reason to stop blocking yet...
// this line is never reached, because it blocks above forever.
fmt.Println(<-ch)
The second program splits the send off into its own line of execution, so now we have:
ch := make(chan int) // make a new int channel
go func () { // start a new line of execution
ch <- 1 // block this second execution thread until we can send to that channel
}()
fmt.Println(<-ch) // block the main line of execution until we can read from that channel
Since those two lines of execution can work independently, the main line can get down to fmt.Println and try and receive from the channel. The second thread will wait to send until it has.
The go routine absolutely makes a difference. The go routine that writes to the channel will be blocked until your main function is ready to read from the channel in the print statement. Having two concurrent threads, one that reads and one that writes fulfills the readiness on both sides.
In your first example, the single thread gets blocked by the channel write statement and will never reach the channel read.
You need to have a concurrent go routine to read from a channel whenever you write to it. Concurrency goes hand-in-hand with channel usage.
Here is a simple example code about unbuffered channels:
ch01 := make(chan string)
go func() {
fmt.Println("We are in the sub goroutine")
fmt.Println(<-ch01)
}()
fmt.Println("We are in the main goroutine")
ch01 <- "Hello"
The result I got:
We are in the main goroutine
We are in the sub goroutine
Hello
Go playground:
https://play.golang.org/p/rFWQbwXRzGw
From my understanding, the send operation blocked the main goroutine, until the sub goroutine executed a receive operation on channel ch01. Then the program exited.
After placing the sub goroutine after the send operation like that:
fmt.Println("We are in the main goroutine")
ch01 <- "Hello"
go func() {
fmt.Println("We are in the sub goroutine")
fmt.Println(<-ch01)
}()
A deadlock occurred:
We are in the main goroutine
fatal error: all goroutines are asleep - deadlock!
go playground
https://play.golang.org/p/DmRUiBG4UmZ
What happened this time? Did that mean after ch01 <- "Hello" the main goroutine was immediately blocked so that the sub goroutine had no chance to run? If it is true, how should I understand the result of the first code example?(At first in main goroutine, then in sub goroutine).
An unbuffered channel blocks on send until a receiver is ready to read. In your first example a reader is set up first, so when the send occurs it can be sent immediately.
In your second example, the send happens before a receiver is ready so the send blocks and the program deadlocks.
You could fix the second example by making a buffered channel, but there is a chance you won't ever see the output from the goroutine as the program may exit (the main goroutine) before the output buffer is flushed. The goroutine may not even run as main exits before it can be scheduled.
First of all, go-routines run concurrently. In 1st example, the sub-goroutine has already started, but in 2nd example, the go-routine hasn't started yet when the send operation appears.
Think about line by line.
In 1st example, the sub-goroutine has started concurrently before the send operation appears on the main go-routine. As a result, when the the send operation happens, there is already an receiver (sub-goroutine) exists.
If you tweak the 1st example,
package main
import (
"fmt"
"time"
)
func main() {
ch01 := make(chan string)
go func() {
fmt.Println("We are in the sub goroutine")
fmt.Println(<-ch01)
}()
// wait for start of sub-routine
time.Sleep(time.Second * 2)
fmt.Println("We are in the main goroutine")
ch01 <- "Hello"
// wait for the routine to receive and print the string
time.Sleep(time.Second * 2)
}
The output will be
We are in the sub goroutine
We are in the main goroutine
Hello
So, you can see that the sub-goroutine has already started.and it is waiting to receive on channel. When the main goroutine send string in channel, the sub-goroutine resumes and receives the signal.
But in 2nd example, The program has stuck in main go routine send operation, and the sub go routine has not started yet and will not start, because the program has not got that line yet. so there is no other receiver to receive the signal. So the program stuck in deadlock.
For unbuffered channels the go routine is blocked until there is no one to receive it. First there should be a go routine to receive the value from the channel and then a value to the channel is send. For the example when we are sending a value to channel it is required to create a buffered channel so that the value is saved into buffered until there is no one to receive it like this will work.
package main
import (
"fmt"
"time"
)
func main() {
ch01 := make(chan string, 10)
ch01 <- "Hello"
go func() {
fmt.Println("We are in the sub goroutine")
fmt.Println(<-ch01)
}()
fmt.Println("We are in the main goroutine")
time.Sleep(1 * time.Second)
}
Playground
Did that mean after ch01 <- "Hello" the main goroutine was immediately
blocked so that the sub goroutine had no chance to run? If it is true,
how should I understand the result of the first code example?(At first
in main goroutine, then in sub goroutine).
It's true. You understand things write. Order of evaluation of spawned goroutines unspecified and can only be controlled with sync tools(channels, mutexes). Sub goroutine in first example may as well Print() first in another environment. It's just unspecified.
I'm new to Go and am stumped on what appears to be a somewhat-rare race condition with a very small block of code running on Linux with Go version 1.2.
Basically, I create a channel for an int, start a go routine to read from the channel, and then write a single int to the channel.
package main
import "fmt"
func main() {
channel := make(chan int)
go func() {
number := <- channel
fmt.Printf("GOT IT: %d\n", number)
}()
fmt.Println("[+] putting num on channel")
channel <- 42
fmt.Println("[-] putting num on channel")
}
The output about 90% of the time is as expected:
$ go run test.go
[+] putting num on channel
GOT IT: 42
[-] putting num on channel
However, about 10% of the time, the go routine simply does not read the number from the channel and prints nothing:
$ go run test.go
[+] putting num on channel
[-] putting num on channel
I'm puzzled because this code is very similar to the example at https://gobyexample.com/channels, (which I do not have this problem with) except that I'm reading from the channel in my go routine instead of writing to the channel.
Do I have a fundamental misunderstanding of how channels work or is there something else at play here?
You should wait until your goroutine executes, and then your, for example, you can do it with sync.WaitGroup:
package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
channel := make(chan int)
wg.Add(1)
go func() {
number := <-channel
fmt.Printf("GOT IT: %d\n", number)
wg.Done()
}()
fmt.Println("[+] putting num on channel")
channel <- 42
wg.Wait()
fmt.Println("[-] putting num on channel")
}
(goplay: http://play.golang.org/p/VycxTw_4vu)
Also you can do it with a "notification channel", that indicates that job is done:
package main
import "fmt"
func main() {
channel := make(chan int)
done := make(chan bool)
go func() {
number := <-channel
fmt.Printf("GOT IT: %d\n", number)
done <- true
}()
fmt.Println("[+] putting num on channel")
channel <- 42
<-done
fmt.Println("[-] putting num on channel")
}
(goplay: http://play.golang.org/p/fApWQgtr4D)
You seem to be expecting the receiving goroutine to run to completion before the second fmt.Println executes. This is not guaranteed to be the case. If the program terminates, goroutines are not guaranteed to reach the end of their functions.
When you see the output that doesn't display the "GOT IT" message, the channel delivered its message, but the main function completed before the goroutine did. The program terminated, and the goroutine never gets the chance to call fmt.Printf
In the example you cited, the main function ends with this:
go func() { messages <- "ping" }()
msg := <-messages
fmt.Println(msg)
Since the main function blocks until it receives a message, the goroutine always runs to completion in this example. In your code, your goroutine executes a step after it receives from the channel, and it's undefined whether the goroutine or the main function will execute the next line after the receive.
You have two goroutines, one in main() (which is implicitly a goroutine), and the anonymous one.
They communicate over a synchronous channel, so after the channel communication, it's guaranteed that they're synchronised.
At this point, the code left in the main() goroutine looks like this:
fmt.Println("[-] putting num on a channel")
and the code left in the anonymous goroutine looks like this:
fmt.Println("GOT IT: %d\n", number)
Now you're racing: the output from these Printlns may appear in either order, or even intermingled. When the Println() from main finishes, the next thing that will happen on that goroutine is that your program will be stopped. This may prevent some or all of the Println from the anonymous goroutine from appearing.