Buffered Channels in Go

Buffered Channels in Go

In this tutorial, we are going to discuss about buffered channels in Go language.

All the channels we discussed in the previous tutorial were basically unbuffered. As we discussed in the channels tutorial in detail, sends and receives to an unbuffered channel are blocking.

Channels can be defined as a pipes using which Goroutines communicate. Similar to water flows from one end to another in a pipe, data can be sent from one end and received from the another end using channels.

By default channels are unbuffered, which states that they will only accept sends (chan <-) if there is a corresponding receive (<- chan) which are ready to receive the sent value.

Buffered channels allows to accept a limited number of values without a corresponding receiver for those values. It is possible to create a channel with a buffer.

It is possible to create a channel with a buffer. Buffered channel are blocked only when the buffer is full. Similarly receiving from a buffered channel are blocked only when the buffer will be empty.

Buffered channels can be created by passing an additional capacity parameter to the make function which specifies the size of the buffer.

ch := make(chan type, capacity)  

Here, capacity in the above syntax should be greater than 0 for a channel to have a buffer. The capacity for an unbuffered channel is 0 by default and hence it omit the capacity parameter.

Lets write some code and create a buffered channel.

package main

import (
    "fmt"
)

func main() {
    ch := make(chan string, 5)
    ch <- "Ashok Kumar"
    ch <- "Sai"
    ch <- "Rama"
    ch <- "Seetha"

    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Output

Ashok Kumar 
Sai 
Rama 
Seetha

Run in playground

In the above example we create a buffered channel with a capacity of 5. Since the channel has a capacity of 5, it is possible to write 5 strings into the channel without being blocked.

We write 4 strings to the channel and the channel does not block. We read the 4 strings respectively.

Another Example

Lets look at one more example of buffered channel in which the values to the channel are written in a concurrent Goroutine and read from the main Goroutine.

This example will help us better understand when writes to a buffered channel block.

package main

import (
    "fmt"
    "time"
)

func write(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
        fmt.Println("Successfully wrote", i, "to channel")
    }
    close(ch)
}

func main() {
    ch := make(chan int, 2)
    go write(ch)
    time.Sleep(1 * time.Second)
    for v := range ch {
        fmt.Println("read value", v, "from channel")
        time.Sleep(1 * time.Second)
    }
}

Output

Successfully wrote 0 to channel 
Successfully wrote 1 to channel 
read value 0 from channel 
Successfully wrote 2 to channel 
read value 1 from channel 
Successfully wrote 3 to channel 
read value 2 from channel 
Successfully wrote 4 to channel 
read value 3 from channel 
read value 4 from channel

Run in playground

In the above example, a buffered channel ch of capacity 2 is created in main Goroutine and passed to the write Goroutine. Then the main Goroutine sleeps for 1 second.

During this time, the write Goroutine is running concurrently. The write Goroutine has a for loop which writes numbers from 0 to 4 to the ch channel.

The capacity of this buffered channel is 2 and hence the write Goroutine will be able to write values 0 and 1 to the ch channel immediately and then it blocks until at least one value is read from ch channel.

So this program will print the following 2 lines immediately.

Successfully wrote 0 to channel 
Successfully wrote 1 to channel 

After printing the above two lines, the writes to the ch channel in the write Goroutine are blocked until someone reads from the ch channel.

Since the main Goroutine sleeps for 1 second before starting to read from the channel, the program will not print anything for the next 1 second.

The main Goroutine wakes up after 1 second and starts reading from the ch channel using a for range loop and prints the read value and then sleeps for 1 second again and this cycle continues until the ch is closed. So the program will print the following lines after 1 second,

read value 0 from channel 
Successfully wrote 2 to channel 

This will continue until all values are written to the channel and it is closed in the write Goroutine. The final output is shown in output section.

Deadlock
package main

import (
    "fmt"
)

func main() {
    ch := make(chan string, 3)
    ch <- "Ashok Kumar"
    ch <- "Sai"
    ch <- "Rama"
    ch <- "Seetha"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

Output

fatal error: all goroutines are asleep - deadlock! 

goroutine 1 [chan send]: 
main.main() 
     /tmp/sandbox206514869/prog.go:12 +0xb9

Run in playground

In the above example, we write 4 strings to a buffered channel of capacity 3. When the control reaches the fourth write, the write is blocked since the channel has exceeded its capacity.

Now some Goroutine must read from the channel in order for the write to proceed, but in this case there is no concurrent routine reading from this channel.

Hence there will be a deadlock and the program will panic at run time.

Buffered Channels in Go

Scroll to top