1

I just started learning golang, while I was going through concurrency I accidentally wrote this code:


import ( 
    "fmt"
)

func squares(c chan int) {
    for i := 0; i < 4; i++ {
        num := <- c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main start")
    
    c := make(chan int)
    
    go squares(c)
    
    c <- 1
    c <- 2
    c <- 3
    c <- 4
    
    go squares(c)
    
    c <- 5
    c <- 6
    c <- 7
    c <- 8
    
    fmt.Println("main stop")
}

Originally I was suppose to assign c := make(chan int, 3).

I am having trouble understanding the output of the code I've written.

main start
1
4
9
25
36
49
16
main stop

I would like to understand how the code is executed. I was expecting error: all goroutines are asleep - deadlock!

Many thanks!

4
  • Why were you expecting it to deadlock? Commented Jul 13, 2020 at 12:50
  • 1
    At which statement do you expect a deadlock? Commented Jul 13, 2020 at 13:05
  • @ShabbirChatrissa but why were you expecting that? Commented Jul 13, 2020 at 14:20
  • @Adrian, clear now. My understanding was lacking. The answer below cleared it up for me. Commented Jul 13, 2020 at 14:40

1 Answer 1

2

I am not sure to really understand what you wanted to achieve, especially the reason of that weird loop :

    for i := 0; i < 4; i++ {
        num := <- c
        fmt.Println(num * num)
    }

But anyway. First have all, some explanations on how works channels and goroutines.

A Channel is a thread safe messaging pipe used to share data accross differents executions contexts. A channel is creating with make instruction, then

c := make(chan int, 3)

means create a channel of int type with a "buffer" size of 3. This element is very important to understand. Channel do follow a producer / consumer pattern with that bases rules :

For producer :

  • if I try to push some data in a channel with some "free space", it doesn't block and next instructions are executed
  • If I try to push some data in a channel without "free space", it blocks till previous has been treated

For consumer :

  • If I try to take element from an empty channel, it block untill some data come
  • If I try to take element from a non empty channel, it take the first (Channel is a FIFO)

Please note that all blocking operations may be turned non-blocking using some special pattern available through select instruction, but that's another subject :).

Goroutine are light sub-processes, routines (No thread). It is no place here to explain all in details, but what is important is 1 goroutine === 1 execution stack.

So in your case, the output is quite predictable till there is only one consumer. Once you start the second Goroutine, the consumption order is still predictable (the size of the channel is only one), but the execution order not !

One thing is noticable: you have only 7 output... That because your main goroutine ends before it happends, terminating the whole program execution. That point explain why you didn't have the all goroutines are asleep - deadlock!.

If you want to have that, you just should add <- c somewhere at the end of the main :

package main

import (
    "fmt"
)

func squares(c chan int) {
    for i := 0; i < 4; i++ {
        num := <-c
        fmt.Println(num * num)
    }
}

func main() {
    fmt.Println("main start")

    c := make(chan int)

    go squares(c)

    c <- 1
    c <- 2
    c <- 3
    c <- 4

    go squares(c)

    c <- 5
    c <- 6
    c <- 7
    c <- 8

    <-c
    fmt.Println("main stop")
}

You will have the behavior I think you expect :

main start
1
4
9
25
36
49
64
16
fatal error: all goroutines are asleep - deadlock!

Edit : on step by step, execution :

    // a goroutine is created and c is empty. because
    // the code of `squares` act as a consumer, the goroutine
    // "block" at instruction `num := <-c`, but not the main goroutine
    go squares(c)
    
    // 1 is pushed to the channel. Till "c" is not full the main goroutine
    // doesn't block but the other goroutine may be "released"
    c <- 1

    // if goroutine of squares has not consume 1 yet, main goroutine block   
    // untill so, but is released just after
    c <- 2

    // it continues with same logic
    c <- 3
    c <- 4

    // till main goroutine encountered `<- c` (instruction I added) .
    // Here, it behave as a consumer of "c". At this point all
    // goroutine are waiting as consuler on "c" => deadlock
Sign up to request clarification or add additional context in comments.

2 Comments

Yes. There is no blocking issue here. You always have at least one producer and one consumer for the channel "c". I will add some detailed on the first execution steps.
thanks a ton! @JeromeDoucet, really helped me in understanding behaviour of channels

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.