Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple goroutines listening on one channel

I have multiple goroutines trying to receive on the same channel simultaneously. It seems like the last goroutine that starts receiving on the channel gets the value. Is this somewhere in the language spec or is it undefined behaviour?

c := make(chan string) for i := 0; i < 5; i++ {     go func(i int) {         <-c         c <- fmt.Sprintf("goroutine %d", i)     }(i) } c <- "hi" fmt.Println(<-c) 

Output:

goroutine 4 

Example On Playground

EDIT:

I just realized that it's more complicated than I thought. The message gets passed around all the goroutines.

c := make(chan string) for i := 0; i < 5; i++ {     go func(i int) {         msg := <-c         c <- fmt.Sprintf("%s, hi from %d", msg, i)     }(i) } c <- "original" fmt.Println(<-c) 

Output:

original, hi from 0, hi from 1, hi from 2, hi from 3, hi from 4 

NOTE: the above output is outdated in more recent versions of Go (see comments)

Example On Playground

like image 991
Ilia Choly Avatar asked Mar 30 '13 06:03

Ilia Choly


People also ask

Can multiple Goroutines write to same channel?

Yes, exactly. This way channels can be used to synchronize execution not only to pass data.

Are Goroutines concurrent?

Goroutines are functions or methods that run concurrently with other functions or methods. Goroutines can be thought of as lightweight threads. The cost of creating a Goroutine is tiny when compared to a thread. Hence it's common for Go applications to have thousands of Goroutines running concurrently.

Can you do something in Goroutines without channels?

Channels not only work for interactions between a goroutine and the main programs, they also provide a way to communicate between different goroutine. For example, let's create a function that subtracts 3 to every result returned by timesThree but only if it's an even number.

What are a way to synchronize the access of shared resources between Goroutines?

6.4. Another way to synchronize access to a shared resource is by using a mutex . A mutex is named after the concept of mutual exclusion. A mutex is used to create a critical section around code that ensures only one goroutine at a time can execute that code section.


2 Answers

Yes, it's complicated, But there are a couple of rules of thumb that should make things feel much more straightforward.

  • prefer using formal arguments for the channels you pass to go-routines instead of accessing channels in global scope. You can get more compiler checking this way, and better modularity too.
  • avoid both reading and writing on the same channel in a particular go-routine (including the 'main' one). Otherwise, deadlock is a much greater risk.

Here's an alternative version of your program, applying these two guidelines. This case demonstrates many writers & one reader on a channel:

c := make(chan string)  for i := 1; i <= 5; i++ {     go func(i int, co chan<- string) {         for j := 1; j <= 5; j++ {             co <- fmt.Sprintf("hi from %d.%d", i, j)         }     }(i, c) }  for i := 1; i <= 25; i++ {     fmt.Println(<-c) } 

http://play.golang.org/p/quQn7xePLw

It creates the five go-routines writing to a single channel, each one writing five times. The main go-routine reads all twenty five messages - you may notice that the order they appear in is often not sequential (i.e. the concurrency is evident).

This example demonstrates a feature of Go channels: it is possible to have multiple writers sharing one channel; Go will interleave the messages automatically.

The same applies for one writer and multiple readers on one channel, as seen in the second example here:

c := make(chan int) var w sync.WaitGroup w.Add(5)  for i := 1; i <= 5; i++ {     go func(i int, ci <-chan int) {         j := 1         for v := range ci {             time.Sleep(time.Millisecond)             fmt.Printf("%d.%d got %d\n", i, j, v)             j += 1         }         w.Done()     }(i, c) }  for i := 1; i <= 25; i++ {     c <- i } close(c) w.Wait() 

This second example includes a wait imposed on the main goroutine, which would otherwise exit promptly and cause the other five goroutines to be terminated early (thanks to olov for this correction).

In both examples, no buffering was needed. It is generally a good principle to view buffering as a performance enhancer only. If your program does not deadlock without buffers, it won't deadlock with buffers either (but the converse is not always true). So, as another rule of thumb, start without buffering then add it later as needed.

like image 111
Rick-777 Avatar answered Sep 17 '22 15:09

Rick-777


Late reply, but I hope this helps others in the future like Long Polling, "Global" Button, Broadcast to everyone?

Effective Go explains the issue:

Receivers always block until there is data to receive.

That means that you cannot have more than 1 goroutine listening to 1 channel and expect ALL goroutines to receive the same value.

Run this Code Example.

package main  import "fmt"  func main() {     c := make(chan int)      for i := 1; i <= 5; i++ {         go func(i int) {         for v := range c {                 fmt.Printf("count %d from goroutine #%d\n", v, i)             }         }(i)     }      for i := 1; i <= 25; i++ {         c<-i     }      close(c) } 

You will not see "count 1" more than once even though there are 5 goroutines listening to the channel. This is because when the first goroutine blocks the channel all other goroutines must wait in line. When the channel is unblocked, the count has already been received and removed from the channel so the next goroutine in line gets the next count value.

like image 31
Brenden Avatar answered Sep 18 '22 15:09

Brenden