This is a creation in Article, where the information may have evolved or changed.
The previous article mainly discusses the situation of deadlock-free channel, this article we continue to discuss another type of channel-buffer channel (buffered channels).
Basic properties
The buffered channel, as its name implies, is a channel with a buffer (buffered). The buffer is used as a temporary storage area for data and can be used as a temporary space to store data. Initialize as follows:
var ch = make(chan int, 1)
The second parameter of make represents the length of the buffer, that is, channel CH will not hang when it receives the first message, it will store the message in the buffer waiting to be received by the goroutine to lift it away. If you do not mention it at this time and the new message arrives, the channel will block and hang.
Give an example in more detail:
func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3}
In this code, channel CH can cache three data, and in the incoming data the main function will hang and return a deadlock error.
One difference between buffered and unbuffered channels is that when there is no full capacity, the buffer channel can complete the transmission and extraction of data in the same goroutine:
import "fmt"func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 fmt.Println(<-ch) fmt.Println(<-ch) fmt.Println(<-ch)}
The advantage of using the queue attributes of the channel mentioned earlier is that the buffered channel can be used as a thread-safe queue in the case of a non-full capacity.
Read mode for channel messages
How to receive channel data in addition to a single read (as in the example code above), go also provides the range keyword:
import "fmt"func main() { ch := make(chan int, 3) ch <- 1 ch <- 2 ch <- 3 // close(ch) for value := range ch { fmt.Println(value) }}
However, the above code will be reported deadlock error after execution, because range does not automatically detect whether the channel is dry (drained), after extracting all the data, re-extract will cause the main function to hang. The solution also has two, the first is to set the length detection in the For loop, if the channel buffer is empty, jump out of the loop, and the second is to close the channel after accepting all the data. It is important to note that the closed state of the channel is never blocked.
The second method reveals another feature of the channel: it is no longer possible to receive new data for the closed channel, but it can attempt to extract the persisted data.
Buffered channel with a capacity of 1
Depending on the characteristics of the channel, it is not difficult to find out that if the channel capacity is set to 1, we can use it as a semaphore (semaphore) to protect the thread safety of shared variables (GKFX variable).
// gop1.io/ch9/bank2var ( sema = make(chan struct{}, 1) balance int)func Deposit(amount int) { sema <- struct{} // acquire token balance = balance + amount <- sema // release token}func Balance() int { sema <- struct{} // acquire token b := balance <-sema // release token return b}
This lock for shared variables We call the two-dollar semaphore (binary semaphore). Since the two-dollar semaphore is very useful, go even offers a dedicated sync library to help us use locks more conveniently. The above code can be rewritten as follows:
import "sync"var ( mu sync.Mutex // guards balance balance int)func Deposit(amount int) { mu.Lock() balance = balance + amount mu.Unlock()}func Balance() int { mu.Lock() defer mu.Unlock() // maybe more operations here return balance}
In the balance function we use defer to prevent the lock from being lifted by error or panic.
Reference:donovan, Alan AA, and Brian W. Kernighan. The Go programming language. Addison-wesley Professional, 2015.
- End -