I have this code from Go tour:
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
fmt.Printf("Sending %d to chan\n", sum)
c <- sum // send sum to c
}
func main() {
s := []int{2, 8, -9, 4, 0, 99}
c := make(chan int)
go sum(s[len(s)/2:], c)
go sum(s[:len(s)/2], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
Produces this output:
Sending 1 to chan
Sending 103 to chan
1 103 104
In this, x gets the second sum and y gets the first sum. Why is the order reversed?
Similar to goroutines order of execution
If you run it multiple times, it may give different result. When I run this I get:
Sending 103 to chan
Sending 1 to chan
103 1 104
If you want result to be deterministic. You may use two channels:
func main() {
s := []int{2, 8, -9, 4, 0, 99}
c1 := make(chan int)
c2 := make(chan int)
go sum(s[len(s)/2:], c1)
go sum(s[:len(s)/2], c2)
x, y := <-c1, <-c2 // receive from c
fmt.Println(x, y, x+y)
}
There are no guarantees in the execution ordering of goroutines. When you start multiple goroutines, they may or may not execute in the order you expect them unless there are explicit synchronizations between them, such as channels or other synchronization primitives.
In your case, the second goroutine writes the channel before the first can, because there is no mechanism to enforce ordering between the two goroutines.
The golang spec says about the channels:
Channels act as first-in-first-out queues. For example, if one
goroutine sends values on a channel and a second goroutine receives
them, the values are received in the order sent.
If you combine the above statement with the arbitrary order of the goroutines execution it could lead to arbitrary order of enqueuing items to channel.
NB: A channel is an abstraction of the CSP.
Related
I am going through Go's official tutorial and have difficulty understanding the difference between Channel and Buffered Channels. The links to the tutorials are https://tour.golang.org/concurrency/2 and https://tour.golang.org/concurrency/3
In the Channel tutorial, Channel c first received the sum of [7, 2, 8] which is 17 and then received the sum of [-9, 4, 0] which is -5. When reading from c, it first output -5 to x and then 17 to y, in LIFO order:
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
(The above output is -5 17 12)
In the Buffered Channel tutorial, the output is 1 2, in FIFO order:
func main() {
ch := make(chan int, 2)
ch <- 1
ch <- 2
fmt.Println(<-ch)
fmt.Println(<-ch)
}
Why are they different?
The chnnael c, in your 1st example of unbuffered channel, is not acting as LIFO.
Actually it is happening because of go routines. The go routines executes concurrently.
If you tweak your code to debug, add one extra line in sum to print the sum before sending to channel.
package main
import "fmt"
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
fmt.Println("slice:", s)
fmt.Println(sum)
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:2], c)
go sum(s[2:4], c)
go sum(s[4:6], c)
x, y, z := <-c, <-c, <-c // receive from c
fmt.Println(x, y, z, x+y+z)
}
The output is:
slice: [4 0]
4
slice: [7 2]
9
slice: [8 -9]
-1
4 9 -1 12
So, you can see that x receives the 1st number that was sent through channel.
Furthermore, unbuffered channels sends data directly to receiver.
If you wanna know about the architecture of channels in go, you can watch this talk of gophercon-2017. I found this talk very helpful.
official code example
func sum(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum // send sum to c
}
func main() {
s := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(s[:len(s)/2], c)
go sum(s[len(s)/2:], c)
x := <-c
y := <-c
fmt.Println(x, y, x+y)
}
printed : -5, 17, 12
why not printed 17, -5, 12 ?
I think x should be 17
There's no defined order of which value gets written to the channel first, it's purely at the mercy of the scheduler. As all the example is doing is adding the results, it doesn't actually matter which way round the results turn up.
There is no guarantee in what order your go sum(...) goroutines will start and which will be the first to finish (and send to channel).
I'm trying to understand this piece of code, not sure why the 2nd go is executed before the 1st one. It'd be great if someone can really help me out with this!
func sum(a []int, c chan int) {
fmt.Println("summing: ", a)
total := 0
for _, v := range a {
total += v
}
//fmt.Println("send to c",total)
c <- total // send total to c
}
func main() {
//a := []int{7, 2, 8,134,23,23,1,23,1234,143, -9, 4, 0, 1234}
c := make(chan int)
go sum([]int{1,2,3}, c)
go sum([]int{4,5,6}, c)
x := <-c
fmt.Println(x)
x = <-c
fmt.Println(x)
}
OUTPUT:
summing: [4 5 6]
15
summing: [1 2 3]
6
You have nothing explicitly synchronizing the order of the two goroutines. If you run this enough times, you will see the calls to fmt.Println print in different sequences. When executing goroutines, as they are concurrent operations, you have no guarantees when they will execute and/or complete. You need to use various standard library packages, or channels themselves to synchronize the execution of concurrently running goroutines.
For example (by leveraging the blocking nature of channels, you could do something like):
func main() {
c := make(chan int)
go sum([]int{1, 2, 3}, c)
//use the channel to block until it receives a send
x := <-c
fmt.Println(x)
//then execute the next routine
go sum([]int{4, 5, 6}, c)
x = <-c
fmt.Println(x)
}
Another example (significantly less practical, but here to look at other common go synchronization features) you could introduce a wait group, and a range over a channel:
func sum(a []int, c chan int, wg *sync.WaitGroup) {
defer wg.Done()
fmt.Println("summing: ", a)
total := 0
for _, v := range a {
total += v
}
//fmt.Println("send to c",total)
c <- total // send total to c
}
func main() {
c := make(chan int)
wg := new(sync.WaitGroup)
//concurrently call the concurrent calls to sum, allowing execution to continue to the range of the channel
go func() {
//increment the wait group, and pass it to the sum func to decrement it when it is complete
wg.Add(1)
go sum([]int{1, 2, 3}, c, wg)
//wait for the above call to sum to complete
wg.Wait()
//and repeat...
wg.Add(1)
go sum([]int{4, 5, 6}, c, wg)
wg.Wait()
//all calls are complete, close the channel to allow the program to exit cleanly
close(c)
}()
//range of the channel
for theSum := range c {
x := theSum
fmt.Println(x)
}
}
package main
import "fmt"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[len(a)/2:], c)
go sum(a[:len(a)/2], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
x, y := <-c, <-c // receive from c
Why does this line always print the same result?
I think it should be 50/50 chance to print
17 -5 12
or
-5 17 12
I think the two go routines should be parallel
Thanks in advance!
package main
import "fmt"
import "time"
import "math/rand"
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
time.Sleep(time.Millisecond * time.Duration(rand.Intn(1000)))
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[len(a)/2:], c)
go sum(a[:len(a)/2], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x+y)
}
I make the thread to sleep, but same thing happens. I am still confusing.
The assumption that it should be a 50/50 chance is incorrect in this situation. To be clear, I'm not suggesting it should always be one or the other but rather that you shouldn't expect it to change each time the program runs. The behavior is not necessarily random (or even pseudo-random).
A goroutine's guarantee is not that it will be scheduled at a random future time, but rather just that it will run at some time in the future. If the current implementation of the scheduler decided to put goroutines in a simple queue, it wouldn't automatically mean it's broken. For your particular code's case, if you stick a few time.Sleeps or fmt.Printfs in different places, you'd likely see the order change around sometimes.
I wonder if you're confusing Go's scheduler's selection of the next goroutine with the documented pseudo-random behavior of select when used with channels. The behavior there is defined to be random and it's correct to say the behavior should have a 50/50 chance.
As far as I've read, the choice of which goroutine the runtime will select is not random. That's not the same as saying it can't be random, but moreso that it's not supposed to be random.
The important thing is that your code shouldn't care about the order in which goroutines are scheduled. Whether it's always in order like a queue, or backwards (a stack), or random, or something else (likely the reality).
I am trying to program a simple Go script that calculates the sum of the natural numbers up to 8:
package main
import "fmt"
func sum(nums []int, c chan int) {
var sum int = 0
for _, v := range nums {
sum += v
}
c <- sum
}
func main() {
allNums := []int{1, 2, 3, 4, 5, 6, 7, 8}
c1 := make(chan int)
c2 := make(chan int)
sum(allNums[:len(allNums)/2], c1)
sum(allNums[len(allNums)/2:], c2)
a := <- c1
b := <- c2
fmt.Printf("%d + %d is %d :D", a, b, a + b)
}
However, running this program produces the following output.
throw: all goroutines are asleep - deadlock!
goroutine 1 [chan send]:
main.sum(0x44213af00, 0x800000004, 0x420fbaa0, 0x2f29f, 0x7aaa8, ...)
main.go:9 +0x6e
main.main()
main.go:16 +0xe6
goroutine 2 [syscall]:
created by runtime.main
/usr/local/go/src/pkg/runtime/proc.c:221
exit status 2
Why is my code deadlocking? I am confused because I am using 2 separate channels to calculate the sub-sums. How are the two channels dependent at all?
Your channels are unbuffered, so the c <- sum line in sum() will block until some other routine reads from the other end.
One option would be to add buffers to the channels, so you can write a value to the channel without it blocking:
c1 := make(chan int, 1)
c2 := make(chan int, 1)
Alternatively, if you run the sum() function as a separate goroutine, then it can block while your main() function continues to the point where it reads from the channels.
Yes, you need to add go like
go sum(allNums[:len(allNums)/2], c1)
go sum(allNums[len(allNums)/2:], c2)
or
c1 := make(chan int,1)
c2 := make(chan int,1)
add channel cache.
I haven't used Go in a while, so this may not be the case, but from what I remember you need go to get another goroutine started, so:
go sum(allNums[:len(allNums)/2], c1)
go sum(allNums[len(allNums)/2:], c2)
If sum isn't running on another goroutine, it tries to execute:
c <- sum
But nothing's reading c; the code reading c has not been reached yet because it's waiting for sum to finish, and sum won't finish because it needs to give it to that code first!