Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I close a channel multiple goroutines are sending on?

Tags:

go

channels

I am attempting to do some computation in parallel. The program is designed so that each worker goroutine sends "pieces" of a solved puzzle back to the controller goroutine that waits to receive and assembles everything sent from the worker routines.

What is the idomatic Go for closing the single channel? I cannot call close on the channel in each goroutine because then I could possibly send on a closed channel. Likewise, there is no way to predetermine which goroutine will finish first. Is a sync.WaitGroup necessary here?

like image 298
Bill DeRose Avatar asked Mar 30 '13 04:03

Bill DeRose


People also ask

How do you stop Goroutines?

You can't kill a goroutine from outside. You can signal a goroutine to stop using a channel, but there's no handle on goroutines to do any sort of meta management. Goroutines are intended to cooperatively solve problems, so killing one that is misbehaving would almost never be an adequate response.

Can multiple Goroutines use the same channel?

A channel can be thought of like a pipe between two or more different goroutines that data can be sent through. One goroutine puts data into one end of the pipe and another goroutine gets that same data out.

What does closing a channel do?

Closing a channel indicates that no more values will be sent on it. This can be useful to communicate completion to the channel's receivers.

What happens when you close a channel Golang?

We can close a channel in Golang with the help of the close() function. Once a channel is closed, we can't send data to it, though we can still read data from it. A closed channel denotes a case where we want to show that the work has been done on this channel, and there's no need for it to be open.


2 Answers

Here is an example using the sync.WaitGroup to do what you are looking for,

This example accepts a lenghty list of integers, then sums them all up by handing N parallel workers an equal-sized chunk of the input data. It can be run on go playground:

package main

import (
    "fmt"
    "sync"
)

const WorkerCount = 10

func main() {
    // Some input data to operate on.
    // Each worker gets an equal share to work on.
    data := make([]int, WorkerCount*10)

    for i := range data {
        data[i] = i
    }

    // Sum all the entries.
    result := sum(data)

    fmt.Printf("Sum: %d\n", result)
}

// sum adds up the numbers in the given list, by having the operation delegated
// to workers operating in parallel on sub-slices of the input data.
func sum(data []int) int {
    var sum int

    result := make(chan int)
    defer close(result)

    // Accumulate results from workers.
    go func() {
        for {
            select {
            case value := <-result:
                sum += value
            }
        }
    }()

    // The WaitGroup will track completion of all our workers.
    wg := new(sync.WaitGroup)
    wg.Add(WorkerCount)

    // Divide the work up over the number of workers.
    chunkSize := len(data) / WorkerCount

    // Spawn workers.
    for i := 0; i < WorkerCount; i++ {
        go func(i int) {
            offset := i * chunkSize

            worker(result, data[offset:offset+chunkSize])
            wg.Done()
        }(i)
    }

    // Wait for all workers to finish, before returning the result.
    wg.Wait()

    return sum
}

// worker sums up the numbers in the given list.
func worker(result chan int, data []int) {
    var sum int

    for _, v := range data {
        sum += v
    }

    result <- sum
}
like image 136
jimt Avatar answered Sep 30 '22 21:09

jimt


Yes, This is a perfect use case for sync.WaitGroup.

Your other option is to use 1 channel per goroutine and one multiplexer goroutine that feeds from each channel into a single channel. But that would get unwieldy fast so I'd just go with a sync.WaitGroup.

like image 45
Jeremy Wall Avatar answered Sep 30 '22 20:09

Jeremy Wall