Shabbir Chatrissa
Shabbir Chatrissa

Reputation: 11

Problem Understanding Golang Concurrency Code

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!

Upvotes: 1

Views: 188

Answers (1)

Jerome Doucet
Jerome Doucet

Reputation: 111

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

Upvotes: 2

Related Questions