go routine for range over channels

10,860

Solution 1

This situation caused of output channel of sq function is not buffered. So sq is waiting until next function will read from output, but if sq is not async, it will not happen (Playground link):

package main

import (
    "fmt"
    "sync"
)

var wg sync.WaitGroup

func main() {
    numsCh := gen(3, 4)
    sqCh := sq(numsCh) // if there is no sq in body - we are locked here until input channel will be closed
    result := sq(sqCh) // but if output channel is not buffered, so `sq` is locked, until next function will read from output channel

    for n := range result {
        fmt.Println(n)
    }
    fmt.Println("Process completed")
}

func gen(nums ...int) <-chan int {
    out := make(chan int)
    go func() {
        for _, n := range nums {
            out <- n
        }
        close(out)
    }()
    return out
}

func sq(in <-chan int) <-chan int {
    out := make(chan int, 100)
    for n := range in {
        out <- n * n
    }
    close(out)
    return out
}

Solution 2

Your function creates a channel, writes to it, then returns it. The writing will block until somebody can read the corresponding value, but that's impossible because nobody outside this function has the channel yet.

func sq(in <-chan int) <-chan int {
    // Nobody else has this channel yet...
    out := make(chan int)
    for n := range in {
        // ...but this line will block until somebody reads the value...
        out <- n * n
    }
    close(out)
    // ...and nobody else can possibly read it until after this return.
    return out
}

If you wrap the loop in a goroutine then both the loop and the sq function are allowed to continue; even if the loop blocks, the return out statement can still go and eventually you'll be able to connect up a reader to the channel.

(There's nothing intrinsically bad about looping over channels outside of goroutines; your main function does it harmlessly and correctly.)

Share:
10,860

Related videos on Youtube

Himanshu
Author by

Himanshu

Senior Cloud Engineer, working on IBM cloud.

Updated on June 04, 2022

Comments

  • Himanshu
    Himanshu almost 2 years

    I have been working in Golang for a long time. But still I am facing this problem though I know the solution to my problem. But never figured out why is it happening.

    For example If I have a pipeline situation for inbound and outbound channels like below:

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        for n := range sq(sq(gen(3, 4))) {
            fmt.Println(n)
        }
        fmt.Println("Process completed")
    }
    
    func gen(nums ...int) <-chan int {
        out := make(chan int)
        go func() {
            for _, n := range nums {
                out <- n
            }
            close(out)
        }()
        return out
    }
    
    func sq(in <-chan int) <-chan int {
        out := make(chan int)
        go func() {
            for n := range in {
                out <- n * n
            }
            close(out)
        }()
        return out
    }
    

    It does not give me a deadlock situation. But if I remove the go routine inside the outbound code as below:

    func sq(in <-chan int) <-chan int {
        out := make(chan int)
        for n := range in {
            out <- n * n
        }
        close(out)
        return out
    }
    

    I received a deadlock error. Why is it so that looping over channels using range without go routine gives a deadlock.

  • Motti
    Motti over 5 years
    I remove the goroutine in sq which was what was actually asked about (your answer still solves the problem). Please re-edit if I changed your intent.
  • Himanshu
    Himanshu over 5 years
    This does not provide my answer. My question was why we are not getting deadlock on wrapping the inbound channels inside go routine. This has nothing to do with the buffer. In buffered channels the channel will add the data to the buffer when receiver is not available.
  • Himanshu
    Himanshu over 5 years
    Thanks but I am still not getting it. I know the reason behind the deadlock. But using go routine will let me range over channels. And one more question comes to my mind now why is it working in the main. It will be appreciated if you provide an reference example for the reason behind do's and don't's.
  • Adrian
    Adrian over 5 years
    You're not getting a deadlock with a goroutine because the writes are in the goroutine. That goroutine blocks, the outer function returns the channel, the caller of that func starts reading from it, and the inner goroutine is then able to send on it.
  • Himanshu
    Himanshu over 5 years
    @Adrian thanks for your comment but It is our assumption. It is not mentioned anywhere inside the docs. Also If I knew that it will help my or anyone in future to know how actually channels wait even if there is no buffer or no value is coming from other end. I have experimented a lot. For example have a look at this play.golang.org/p/owLc5QoAROa.
  • Adrian
    Adrian over 5 years
    The relevant facts are definitely mentioned, ie that channel operations can block, and that goroutines run concurrently. Your specific example is just an application of those fundamentals.
  • iHelos
    iHelos over 5 years
    it's all about different flows of sync and async programms. in case of 'go' keyword usage the program can transfer control of program execution to another goroutine, so buffer can be freed. Sync: 1) Write to channel 2) Write to channel - blocked and the async usage can be like Async: 1) Write to channel 2) Read from channel 3) Write to channel 4) Read from channel