Skip to content

Go Concurrency Channels

Openweb edited this page Nov 9, 2020 · 8 revisions

Introduction

Go-routine is a concurrently executing activity. Golang supports two styles of concurrent programming:

  1. CSP (Communicating Sequential Process) concurrency model, in which values are passed between between go-routines (using channels) but variables are confined to single activity.
  2. Shared Memory Multithreading: in which shared memories is used using mutex primitives.

Channels

Go uses, Channels as communication mechanisms used to pass data among go-routines. A channel is created as:

 ch := make(chan int) // create a synchronous channel to pass ints
 type ints chan<-int
 integers := make(chan ints) // create a synchronous channel, of int channels

Like maps a channel is a reference type. A zero value is nil. Two channels of same type are comparable using ==, true if both reference same underlying data structure.

Channels support three operations:

  1. send ch <- value sends a value to channel, ch
  2. receive value = <- ch receives a value from channel, ch
  3. close(ch), closes a channel:
  • a send on closed channel will cause panic and
  • a receive on closed channel will fetch unread data and then immediately return zero-value if empty

Unbuffered or synchronous channels

Create unbuffered channel by using 0 in capacity argument of make. A Send operation on a unbuffered channel blocks the sending go-routine until data is read from the channel, Playground example.

  ch := make(chan int) // create unbuffered int channel
  wait := make(chan struct{}, 0) // or, create unbuffered message channel

  go func () {
     ch <- 1
     ch <- 2 // Blocks until, 1 ch is read from ch. Blocks forever
     wait <- struct{}{}
  }()
  <- ch // read 1
  <- ch // wait until 2 is written
  <- wait // for anonymous go-routine to finish. Used to explain concept

Buffered channels can be used to:

  1. Synchronize go-routines.
  2. Create pipeline among go-routines
  3. Create unidirectional data paths e.g input channel (mnemonic: <- chan) or output channel (mnemonic: chan<-).

Example of unidirectional channel:

// printers argument is a read only "in" channel. It can not write "in" channel.
func printers (in <-chan int) {}

// squares writes squares in "out" channel of corresponding ints "in" channel. It can not read "out" channel
func squares (out chan<- int, in <-chan int) {}

Closing channels

It is not necessary to close a channel, as channels not closed are reclaimed by garbage collector. It is only necessary to close a channel when sender wants receiver to know that all data has been sent. There are three ways for receiver to know channel is closed:

  1. Use range to read the values
  2. Use pattern v, ok := <- ch if ok is false channel is closed.
  3. Returns zero-value immediately.

See example of closing a channel and receiver determining if a channel is closed using 2 and 3 above.

Using ok Pattern

Similar to maps, channel return ok as true if value is read from channel. And false, it is read from closed channel.

readInts:
for {
	select {
	case x, ok := <-ints:
		if ok == true {
			fmt.Printf("%d ", x)
		} else {
			fmt.Printf("\n2. readInts: Channel is closed.")
			break readInts // channel closed, break out of `readInts:` block
		}
	}
}

Closed channel returns zero-value immediately

// isClosed checks if a channel is closed
func isClosed(ch chan int) bool {
	select {
	case <-ch:
		return true
	default:
		return false
	}
}
Clone this wiki locally