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
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
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
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.