Buffered channel and its blocking behavior
Golang is really unique with its Go routines giving splendid concurrency support. But go routines run as fast as possible and could end longer than the main program or shorter than the main program. If the former, the result of the go routine could be lost as the main program terminates. Often, sync.WaitGroup could be your solution to the main program terminating “prematurely”. But the most common way is using the channel to synchronise the lifespans of the routines and main program. I have talked about the unbuffered channel blocking behavior in this earlier article. So the focus of this article is now on the buffered channel.
Create a buffered channel
out := make(chan string, 5)
This create a channel backed with 5 slots, so that 5 string messages could be queued in the channel.
How about the blocking nature?
Since the buffered channel works like a queue, it’s easy to worry and wonder what happened when the channel is fully filled. When it is filled, the channel will not accept anymore “messages”. You could asked whether is an error thrown or what?? Well, in order to be as non-disruptive to our program to handles errors, the behavior of a filled channel is to block.
To unblock it, the messages have to be taken out from the channel with <- out. Then the routines can continue to fill in more messages with out <- “string”.
Demonstration of behavior
To understand this first-hand, the best thing is to code. I have written a simple program just to demonstrate this behavior.
package bufferchannelsimport (
"fmt"
"log"
"net/http"
)func GetContentHeaders(url string, out chan string, index int) {
client := &http.Client{}
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
log.Fatalln(err)
}
resp, err := client.Do(req)
if err != nil {
log.Fatalln(err)
}
defer resp.Body.Close()fmt.Printf("%d. Before out<-\n", index)
out <- resp.Status
fmt.Printf("%d. After out<-\n", index) //don't see this means channel blocked}
The function GetContentHeaders will take in an url string and string (buffered) channel. It will send a get request via HTTP to the url and retrieve the response. With the response, it send the header string into the buffered channel out. Notice the printf statements before and after the out <- statement. This will help us to visualize whether the routine is block at the out <-statement or not. Specifically if you see a pair of “Before out” and “After out”, it means that channel is not blocked and the header response is successfully filled into the channel.
main.go
Let’s use the function in our main program as below.
package mainimport (
"bufferchannels"
"fmt"
"time"
)func main() { out := make(chan string, 5) //1 url := "https://www.google.com"
for i := 0; i < 10; i++ { go bufferchannels.GetContentHeaders(url, out, i)//2
} for i := 0; i < 2; i++ { //3
fmt.Printf("response header: %q\n", <-out)
} time.Sleep(time.Second * 20)//4
fmt.Println("main ended...")}
We create a buffered channel of size 5 (//1). Then we launched the GetContentHeaders function in the form of 10 go routines (//2). In (//3), we take out only 2 response header strings and print them. At this point, you should can’t help thinking that since the <- out and out<- statements are not equal, the <-out statements in the GetContentHeaders should eventually block. When they do, we only see standalone “Before out” print outs.
So how may of standalone “Before out” statements will we see?
The answer is 3. The buffered channel will quickly filled up with 5 response headers from 5 go routines and will block. Two response headers will be removed and the channel temporarily unblocks, so two more response headers can be filled. Then the channel blocks again. So 7 go routines will complete with 3 go routines blocked at the out<- statements.
Output:
$ go run main.go
5. Before out<-
5. After out<-
response header: "200 OK"
0. Before out<-
0. After out<-
2. Before out<-
2. After out<-
9. Before out<-
9. After out<-
6. Before out<-
6. After out<-
1. Before out<-
1. After out<-
8. Before out<-
8. After out<-
7. Before out<-
response header: "200 OK"
3. Before out<-
4. Before out<-
main ended...
Indeed, in the print output, go routines represented by index 7, 3, 4 were blocking before the whole program ends.
Before we end, the sleep timer in //4 is required since we do not want the main program to ends before all the go routines could run to end or run to blocking state.
Up to this point, I hope you can see for yourself and have deepened your understanding of buffered response.