What is a channel pipeline
It is a data pipeline that can write data into it and read data from it.
The channel is a data communication bridge between goroutines and is thread-safe.
channel follows the first-in first-out principle.
When writing and reading data, they will be locked.
Channels can be divided into 3 types:
- Read-only channel, one-way channel
- Write only channel, one-way channel
- Readable or writeable channel
Channel can also be divided into:
Channel with buffer, defines the buffer size, and can store multiple data
A channel without buffer can only store one data, and only when the data is fetched can the next data be stored.
Basic use of channel
Definition and declaration
// Read-only channelvar readOnlyChan <-chan int // The type of channel is int // Write only channelvar writeOnlyChan chan<- int // Readable or writeablevar ch chan int // Or use make to initialize directlyreadOnlyChan1 := make(<-chan int, 2) // Read-only channel with cache areareadOnlyChan2 := make(<-chan int) // Read-only without cache channelwriteOnlyChan3 := make(chan<- int, 4) // Write only with cache channelwriteOnlyChan4 := make(chan<- int) // Write only without cache channelch := make(chan int, 10) // Readable and writeable with cache areach <- 20 // Write datai := <-ch // Read datai, ok := <-ch // You can also judge the read data
chan_var.go
package main import ( "fmt" ) func main() { // var declares a channel whose zero value is nil var ch chan int ("var: the type of ch is %T \n", ch) ("var: the val of ch is %v \n", ch) if ch == nil { // You can also declare a channel with make, and the value it returns is a memory address ch = make(chan int) ("make: the type of ch is %T \n", ch) ("make: the val of ch is %v \n", ch) } ch2 := make(chan string, 10) ("make: the type of ch2 is %T \n", ch2) ("make: the val of ch2 is %v \n", ch2) } // Output:// var: the type of ch is chan int // var: the val of ch is <nil> // make: the type of ch is chan int // make: the val of ch is 0xc000048060 // make: the type of ch2 is chan string // make: the val of ch2 is 0xc000044060
3 ways to operate a channel
There are generally three ways to operate the channel:
- Read <-ch
- Write ch<-
- Close close(ch)
operate | nil's channel | Normal channel | Closed channel |
---|---|---|---|
Read <-ch | block | Success or blockage | Read zero value |
Write ch<- | block | Success or blockage | panic |
Close close(ch) | panic | success | panic |
Note: For the nil channel case, there is a special scenario:
When nil channel is in a case of select, this case will block, but will not cause deadlock.
One-way channel
One-way channel: read-only and write-only channel
chan_uni.go
package main import "fmt" func main() { // One-way channel, only channel ch := make(chan<- int) go testData(ch) (<-ch) } func testData(ch chan<- int) { ch <- 10 // Run the output// ./chan_uni.go:9:14: invalid operation: <-ch (receive from send-only type chan<- int) // Report an error,It's a write-only send-only channel
Modify the one-way channel initialized in the main() function above to readable and writeable channels, and then run
chan_uni2.go
package main import "fmt" func main() { // Modify the one-way channel initialized by the main() function above to a readable and writable channel ch := make(chan int) go testData(ch) (<-ch) } func testData(ch chan<- int) { ch <- 10 } // Run output:// 10 // No error reported,Can output the results normally
Channels with and without buffering
Without buffer channel
chan_unbuffer.go
package main import "fmt" func main() { ch := make(chan int) // Unbuffered channel go unbufferChan(ch) for i := 0; i < 10; i++ { ("receive ", <-ch) // Read out the value } } func unbufferChan(ch chan int) { ("send ", i) ch <- i // Write value// Outputsend 0 send 1 receive 0 receive 1 send 2 send 3 receive 2 receive 3 send 4 send 5 receive 4 receive 5 send 6 send 7 receive 6 receive 7 send 8 send 9 receive 8 receive 9
with buffer channel
chan_buffer.go
package main import ( "fmt" ) func main() { ch := make(chan string, 3) ch <- "tom" ch <- "jimmy" ch <- "cate" (<-ch) (<-ch) (<-ch) } // Run output:// tom // jimmy // cate
Let's take a look at another example: chan_buffer2.go
package main import ( "fmt" "time" ) var c = make(chan int, 5) func main() { go worker(1) for i := 1; i < 10; i++ { c <- i (i) } } func worker(id int) { for { _ = <-c // Run output:// 1 // 2 // 3 // 4 // 5 // 6 // 7 // 8 // 9
Determine whether the channel is closed
if v, ok := <-ch; ok { (ch) }
illustrate:
- ok is true, the data is read, and the pipeline is not closed
- ok is false, the pipeline is closed, no data is readable
Reading a closed channel will read a zero value. If you are not sure whether the channel is closed, you can use this method to detect it.
range and close
range can iterate through arrays, maps, strings, channels, etc.
A sender can close the channel, indicating that no data is sent to the channel. The receiver can also test whether the channel is closed, throughv, ok := <-ch
The ok value in the expression determines whether the channel is closed. The previous section has stated that when ok is false, it means that the channel has not received any data and it has been closed.
Note: Only the sender can close a channel, not the receiver. Sending data to a closed channel will result in panic.
Note: channels are not files, you usually don't need to close them. Then when does it need to be closed? This is required when you want to tell the recipient that there is no value sent to the channel.
For example, terminate the range loop.
When the for range traversal channel, if the sender does not close the channel or closes after the range, it will cause a deadlock (deadlock).
Here is an example that will cause deadlocks:
package main import "fmt" func main() { ch := make(chan int) go func() { for i := 0; i < 10; i++ { ch <- i } }() for val := range ch { (val) } close(ch) // If you close the channel here, you have "notified" and you will not be able to range, and the deadlock will be triggered. // Regardless of whether the channel is closed here, it will be reported as a deadlock, and the location of close(ch) is incorrect. // And the operator who closes the channel is also wrong, it can only be the sender who closes the channel} // Run the program output// 0 // 1 // 2 // 3 // 4 // 5 // 6 // 7 // 8 // 9 // fatal error: all goroutines are asleep - deadlock!
Correction is simple,close(ch)
Move togo func(){}()
In the code, the following
go func() { for i := 0; i < 10; i++ { ch <- i } close(ch) }()
In this way, the program can run normally and there will be no deadlock errors reported.
Write the above program in another way, chang_range.go
package main import ( "fmt" ) func main() { ch := make(chan int) go test(ch) for val := range ch { // ("get val: ", val) } } func test(ch chan int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } // Run output:// get val: 0 // get val: 1 // get val: 2 // get val: 3 // get val: 4
The for range loop automatically exits when the sender closes the channel.
for read channel
Use for to continuously read the data in the channel.
Modify the above range program, chan_for.go
package main import ( "fmt" ) func main() { ch := make(chan int) go test(ch) for { val, ok := <-ch if ok == false {// ok is false, no data can be read break // Break out of the loop } ("get val: ", val) } } func test(ch chan int) { for i := 0; i < 5; i++ { ch <- i } close(ch) } // Run output:// get val: 0 // get val: 1 // get val: 2 // get val: 3 // get val: 4
select Use
Example chan_select.go
package main import "fmt" // /tour/concurrency/5 func fibonacci(ch, quit chan int) { x, y := 0, 1 for { select { case ch <- x: x, y = y, x+y case <-quit: ("quit") return } } } func main() { ch := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { (<-ch) } quit <- 0 }() fibonacci(ch, quit) } // Run output:// 0 // 1 // 1 // 2 // 3 // 5 // 8 // 13 // 21 // 34 // quit
Some usage scenarios of channel
1. Data transfer pipeline as goroutine
package main import "fmt" // /tour/concurrency/2 func sums(s []int, c chan int) { sum := 0 for _, v := range s { sum += v } c <- sum } func main() { s := []int{7, 2, 8, -9, 4, 0} c := make(chan int) go sums(s[:len(s)/2], c) go sums(s[len(s)/2:], c) x, y := <-c, <-c // receive from c (x, y, x+y)
Use goroutine and channel to sum in batches
2. Synchronized channel
Channels without buffers can be used as a pipeline for synchronizing data and play the role of synchronizing data.
When operating a channel without a buffer, the sent goroutine and the received goroutine need to be prepared at the same time, that is, the sending and receiving need to be paired one by one to complete the sending and receiving operations.
If both parties' goroutines are not prepared at the same time, the channel will cause the send or received goroutine to block waiting first. This is what a channel without buffers does as data synchronization.
gobyexampleAn example in:
package main import ( "fmt" "time" ) ///channel-synchronization func worker(done chan bool) { ("working...") () ("done") done <- true } func main() { done := make(chan bool, 1) go worker(done) <-done }
Note: Don't send or receive data in the same goroutine coroutine when synchronized channels. May cause deadlock to be deadlock.
3. Asynchronous channel
A buffered channel can be used as an asynchronous channel.
Channels with buffers also have operational precautions:
If there is no value in the channel and the channel is empty, the receiver will be blocked.
If the buffer in the channel is full, the sender will be blocked.
Note: If the channel with a buffer is used up, you need to close it. Otherwise, the goroutine that handles this channel will be blocked and form a deadlock.
package main import ( "fmt" ) func main() { ch := make(chan int, 4) quitChan := make(chan bool) go func() { for v := range ch { (v) } quitChan <- true // The channel used for notification means that the program here has been executed }() ch <- 1 ch <- 2 ch <- 3 ch <- 4 ch <- 5 close(ch) // Close channel after use <-quitChan // Unblock after receiving the channel notification, this is also a way to use the channel}
Timeout processing
Channel combines time to implement time-out processing.
When a channel reads data for a certain period of time and has not yet arrived, it can get a timeout notification to prevent the current goroutine from being blocked.
chan_timeout.go
package main import ( "fmt" "time" ) func main() { ch := make(chan int) quitChan := make(chan bool) go func() { for { select { case v := <-ch: (v) case <-( * (3)): quitChan <- true ("timeout, send notice") return } } }() for i := 0; i < 4; i++ { ch <- i } <-quitChan // The output value is equivalent to receiving a notification, revoking the main process blocking ("main quit out") }
Precautions and deadlock analysis when using channel
Uninitialized channel read and write shutdown operation
1. Read: Uninitialized channel will cause deadlock when reading the data inside.
var ch chan int <-ch // Not initializedchannelReading data will be deadlocked
2. Write: When writing data into the channel, it will cause deadlock.
var ch chan int ch<- // Not initializedchannelWriting data will be deadlocked
3. Close: Uninitialized channel, when closing the channel, it will panic
var ch chan int close(ch) // Close uninitializedchannel,triggerpanic
Initialized channel read and write shutdown operation
1. Initialized channel without buffer
// Code snippet 1 func main() { ch := make(chan int) ch <- 4 }
Code Snippet 1: There is no buffer channel, and only write or read, will generate a deadlock
// Code Snippet 2 func main() { ch := make(chan int) val, ok := <-ch }
Code Snippet 2: There is no buffer channel, and only reads and no writes, a deadlock will be generated
// Code Snippet 3 func main() { ch := make(chan int) val, ok := <-ch if ok { (val) } ch <- 10 // Write here. But the deadlock has been generated before }
Code Snippet 3: There is no buffer channel, both write and read, but in the codeval, ok := <-c
Deadlock has been generated at the location. The following code cannot be executed.
// Code Snippet 4 func main() { ch := make(chan int) ch <- 10 go readChan(ch) ( * 2) } func readChan(ch chan int) { for { val, ok := <-ch ("read ch: ", val) if !ok { break } } }
Code Snippet 4: There is no buffer channel, both write and read, but after running the program, an error is reportedfatal error: all goroutines are asleep - deadlock!
。
This is because the code that writes data into the channlech <- 10
, a deadlock has occurred when writing data here. Bundlech<-10
andgo readChan(ch)
Change the position and the program can run normally without deadlock.
// Code Snippet 5 func main() { ch := make(chan int) go writeChan(ch) for { val, ok := <-ch ("read ch: ", val) if !ok { break } } () ("end") } func writeChan(ch chan int) { for i := 0; i < 4; i++ { ch <- i
Code Snippet 5: The channel without buffering is both written and read. Unlike the above code snippets, the data written to the channel is not the same.
Think about it, will this program cause deadlocks? Think about it in 10 seconds, don’t look at the following first.
It will also generate a deadlock, and it will report an error after outputting data.fatal error: all goroutines are asleep - deadlock!
。
Why? This program fragment is both read and write, and it is first opened to write a goroutine to write data. Why is it deadlocked?
The reason ismain()
Insidefor
cycle. Maybe you will ask, nobreak
Jump outfor
Loop? The code is written, but the program is not executed here.
becausefor
There will be a constant cycle, andval, ok := <-ch
, hereok
The value has always been true because there is no channel closed in the program. You can print thisok
It's worth checking if it's always true. whenfor
After the loop reads the value in the channel, the program runs againval, ok := <-ch
When , a deadlock occurs because there is no data in the channel.
The reason has been found, and the solution is very simple.writeChan
Close channel in the function, add codeclose(ch)
. Tellfor
I finished writing and closed the channel.
After closing the channel code, run the program:
read ch: 0 , ok: true read ch: 1 , ok: true read ch: 2 , ok: true read ch: 3 , ok: true read ch: 0 , ok: false end
The program outputs the results normally.
For channel (unbuffered channel) without buffers, several code snippets are analyzed that are prone to deadlocks. Let's summarize:
- channel to initialize the channel
- Reads and writes must be paired and cannot be in the same goroutine
- You must first use go to perform read or write operations
- When writing data multiple times, the writer should close the channel (code snippet 5)
2. Initialized channel with buffer
// Code snippet 1func main() { ch := make(chan int, 1) val, ok := <-ch }
Code snippet 1: There is a buffered channel. Read the data first, and it will be blocked all the time, resulting in a deadlock.
// Code Snippet 2 func main() { ch := make(chan int, 1) ch <- 10 }
Code Snippet 2: The same code Snippet 1 has a buffer channel, only write but not read, it will also block and generate a deadlock.
// Code Snippet 3 func main() { ch := make(chan int, 1) ch <- 10 val, ok := <-ch if ok { (val, ok) } }
Code Snippet 3: Buffered channel, read and write, and normal output results.
Summary of channel with buffers:
- If the channel is full, the sender will block
- If the channle is empty, the receiver will block
- If in the same goroutine, the data writing operation must be before the data reading operation
refer to
/tour/concurrency
/ref/spec#Channel_types
/ref/spec#Send_statements
/ref/spec#Receive_operator
/ref/spec#Close
/doc/effective_go#channels
/ref/spec#Select_statements
/
Concurrency is not parallelism - The Go Programming Language
This is the article about the detailed usage, precautions and deadlock analysis of channel in golang. This is the end. For more relevant contents of channel in golang, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!