Whether it is a buffered channel or a buffered channel, there are blocking situations, but in fact, there are some cases where we do not want to read or write data to block there. There is a only solution, that is, use the select structure.
This article will explain what situations can block and how to use select to resolve blocking.
Blocking scenarios
There are 4 blocking scenarios, 2 each with cache and unbuffered.
The characteristic of the unbuffered channel is that the sent data needs to be read before the sending will be completed, which blocks the scenario:
- There is no data in the channel, but the read channel is performed.
- There is no data in the channel, and data is written to the channel, but no coroutine reads.
// Scene 1func ReadNoDataFromNoBufCh() { noBufCh := make(chan int) <-noBufCh ("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // Scene 2func WriteNoBufCh() { ch := make(chan int) ch <- 1 ("write success no block") // Output: // fatal error: all goroutines are asleep - deadlock! }
Note: The Output annotation in the sample code represents the execution result of the function. Each function cannot continue to be executed downward due to blocking operation in the channel, and finally a deadlock error is reported.
The characteristic of a cache channel is that when there is a cache, data can be written into the channel and returned directly. When there is data in the cache, data can be read from the channel and returned directly. At this time, the cache channel will not block. The scenario where it blocks is:
- The cache of the channel has no data, but the read channel is performed.
- The cache of the channel is already full, and data is written to the channel, but there is no coroutine reading.
// Scene 1func ReadNoDataFromBufCh() { bufCh := make(chan int, 1) <-bufCh ("read from no buffer channel success") // Output: // fatal error: all goroutines are asleep - deadlock! } // Scene 2func WriteBufChButFull() { ch := make(chan int, 1) // make ch full ch <- 100 ch <- 1 ("write success no block") // Output: // fatal error: all goroutines are asleep - deadlock! }
Use Select to achieve non-blocking reading and writing
select is a structure that performs selection operations. It has a set of case statements inside, which will execute the non-blocking one. If they are all blocked, then wait for one of them to not block and continue to execute. It has a default statement, which will never block. We can use it to achieve non-blocking operations.
The following example code is the read and write of unbuffered channels and buffered channels modified using select. The following function can be called directly through the main function. The comment of Ouput is the running result. From the results, it can be seen that when the channel is unreadable or unwritable, it will no longer block and wait, but will return directly.
// Unbuffered channel readingfunc ReadNoDataFromNoBufChWithSelect() { bufCh := make(chan int) if v, err := ReadWithSelect(bufCh); err != nil { (err) } else { ("read: %d\n", v) } // Output: // channel has no data } // Buffered channel readingfunc ReadNoDataFromBufChWithSelect() { bufCh := make(chan int, 1) if v, err := ReadWithSelect(bufCh); err != nil { (err) } else { ("read: %d\n", v) } // Output: // channel has no data } // Select structure implements channel readingfunc ReadWithSelect(ch chan int) (x int, err error) { select { case x = <-ch: return x, nil default: return 0, ("channel has no data") } } // Unbuffered channel writingfunc WriteNoBufChWithSelect() { ch := make(chan int) if err := WriteChWithSelect(ch); err != nil { (err) } else { ("write success") } // Output: // channel blocked, can not write } // Write with buffered channelfunc WriteBufChButFullWithSelect() { ch := make(chan int, 1) // make ch full ch <- 100 if err := WriteChWithSelect(ch); err != nil { (err) } else { ("write success") } // Output: // channel blocked, can not write } // Select structure implements channel writingfunc WriteChWithSelect(ch chan int) error { select { case ch <- 1: return nil default: return ("channel blocked, can not write") } }
Improve non-blocking reading and writing using Select+ timeout
Non-blocking channel blocking implemented using default has a defect: it will return when the channel is unreadable or written. In actual scenarios, more demands are that we hope to try to read the data for a while, or try to write the data for a while. If we really can't read and write, we will return and the program will continue to do other things.
Using a timer instead of default can solve this problem. For example, the tolerance time for reading and writing data to the channel is 500ms. If it still cannot read and write, it will return immediately. If it is modified, it will be like this:
func ReadWithSelect(ch chan int) (x int, err error) { timeout := ( * 500) select { case x = <-ch: return x, nil case <-: return 0, ("read time out") } } func WriteChWithSelect(ch chan int) error { timeout := ( * 500) select { case ch <- 1: return nil case <-: return ("write time out") } }
The result will turn into a timeout return:
read time out
write time out
read time out
write time out
The above is all the content of this article. I hope it will be helpful to everyone's study and I hope everyone will support me more.