What is a channel pipe
It’s a data pipe that you can write into and read from.
A channel is a data communication bridge between Goroutines and is thread-safe.
Channel operates on a first-in, first-out basis.
Both write and read data are locked.
Channels can be divided into three types:
-
Read-only channel, one-way channel
-
Write only channels, one-way channels
-
Readable and writable channels
Channels can also be divided into:
-
A buffered channel, with a defined buffer size, can store multiple data
-
A channel with no buffer can store only one data, and can store only one data when the data is retrieved
Basic use of channels
Definitions and declarations
/ / read only channel
var readOnlyChan <-chan int // Channel is of type int
/ / write channel only
var writeOnlyChan chan<- int
// Can read and write
var ch chan int
// Or use make to initialize directly
readOnlyChan1 := make(< -chan int.2) // Read-only channel with cache
readOnlyChan2 := make(< -chan int) // Read-only with no cache channel
writeOnlyChan3 := make(chan<- int.4) // Write only channel with cache
writeOnlyChan4 := make(chan<- int) // Write only and no cache channel
ch := make(chan int.10) // It is readable and writable with cache
ch <- 20 / / write data
i := <-ch / / read the data
i, ok := <-ch // You can also determine the data read
Copy the code
chan_var.go
package main
import (
"fmt"
)
func main(a) {
// var declares a channel whose zero value is nil
var ch chan int
fmt.Printf("var: the type of ch is %T \n", ch)
fmt.Printf("var: the val of ch is %v \n", ch)
if ch == nil {
// We can also declare a channel with make, which returns a memory address
ch = make(chan int)
fmt.Printf("make: the type of ch is %T \n", ch)
fmt.Printf("make: the val of ch is %v \n", ch)
}
ch2 := make(chan string.10)
fmt.Printf("make: the type of ch2 is %T \n", ch2)
fmt.Printf("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
Copy the code
Three ways to operate a channel
There are three ways to operate a channel:
-
Read < – ch
-
Write ch < –
-
Close close (ch)
operation | Nil channel | Normal channel | Closed channel |
---|---|---|---|
Read < – ch | blocking | Success or block | Read zero |
Write ch < – | blocking | Success or block | panic |
Close close (ch) | panic | successful | panic |
Note that for the nil channel case, there is one special scenario:
When a nil channel is in a select case, the case blocks but does not cause a deadlock.
A one-way channel
One-way channel: read-only and write-only channels
chan_uni.go
package main
import "fmt"
func main(a) {
// one-way channel, write channel only
ch := make(chan<- int)
go testData(ch)
fmt.Println(<-ch)
}
func testData(ch chan<- int) {
ch <- 10
}
// Run output
// ./chan_uni.go:9:14: invalid operation: <-ch (receive from send-only type chan<- int)
// It is an send-only channel
Copy the code
Change the one-way channel initialized in main() to a readable and writable channel and run it again
chan_uni2.go
package main
import "fmt"
func main(a) {
// Change the one-way channel initialized by main() to a readable and writable channel
ch := make(chan int)
go testData(ch)
fmt.Println(<-ch)
}
func testData(ch chan<- int) {
ch <- 10
}
// Run output:
/ / 10
// No error is reported, and the result can be output normally
Copy the code
Buffered and unbuffered channels
No buffer channel
chan_unbuffer.go
package main
import "fmt"
func main(a) {
ch := make(chan int) // Unbuffered channel
go unbufferChan(ch)
for i := 0; i < 10; i++ {
fmt.Println("receive ", <-ch) / / read values}}func unbufferChan(ch chan int) {
for i := 0; i < 10; i++ {
fmt.Println("send ", i)
ch <- i / / write values}}/ / output
send 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
Copy the code
Buffered channel
chan_buffer.go
package main
import (
"fmt"
)
func main(a) {
ch := make(chan string.3)
ch <- "tom"
ch <- "jimmy"
ch <- "cate"
fmt.Println(<-ch)
fmt.Println(<-ch)
fmt.Println(<-ch)
}
// Run output:
// tom
// jimmy
// cate
Copy the code
Look at another example: chan_buffer2.go
package main
import (
"fmt"
"time"
)
var c = make(chan int.5)
func main(a) {
go worker(1)
for i := 1; i < 10; i++ {
c <- i
fmt.Println(i)
}
}
func worker(id int) {
for {
_ = <-c
}
}
// Run output:
/ / 1
/ / 2
/ / 3
/ / 4
/ / 5
/ / 6
/ / 7
/ / 8
/ / 9
Copy the code
Check whether the channel is closed
if v, ok := <-ch; ok {
fmt.Println(ch)
}
Copy the code
Description:
- Ok is true, data is read, and the pipe is not closed
- Ok is false, the pipe is closed and there is no data to read
Reading a closed channel reads a zero value, which can be used to check if a channel is closed or not.
range and close
Range can iterate over groups, maps, strings, channels, etc.
A sender can close a channel, indicating that no data is being sent to the channel. The receiver can also test whether a channel is closed by checking the ok value in the v, ok := <-ch expression. As you saw in the previous section, when OK is false, the channel does not receive any data and is closed.
Note: Only the sender closes a channel, not the receiver. Sending data to a closed channel can cause panic.
Note: Channels are not files and you usually don’t need to close them. When does it need to be closed? This is needed when telling the receiver that there is no value to send to the channel.
Like terminating the range loop.
When for range traverses a channel, if the sender does not close the channel or closes it after range, a deadlock will result.
Here is an example of a deadlock that can occur:
package main
import "fmt"
func main(a) {
ch := make(chan int)
go func(a) {
for i := 0; i < 10; i++ {
ch <- i
}
}()
for val := range ch {
fmt.Println(val)
}
close(ch) // If the channel is closed, a deadlock will be triggered.
// If the channel is closed or not, the deadlock will be reported, and the position of close(ch) is wrong.
// The operator who closed the channel was also wrong. Only the sender closed the channel
}
// Run the program output
/ / 0
/ / 1
/ / 2
/ / 3
/ / 4
/ / 5
/ / 6
/ / 7
/ / 8
/ / 9
// fatal error: all goroutines are asleep - deadlock!
Copy the code
Change close(ch) to go func(){}()
go func(a) {
for i := 0; i < 10; i++ {
ch <- i
}
close(ch)
}()
Copy the code
This way, the program can run normally without reporting deadlock errors.
Write the above program in a different way, chan_range.go
package main
import (
"fmt"
)
func main(a) {
ch := make(chan int)
go test(ch)
for val := range ch { //
fmt.Println("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
Copy the code
The for range loop exits automatically when the sender closes a channel.
Read for the channel
Use for to loop through a channel.
Change the range above,chan_for.go
package main
import (
"fmt"
)
func main(a) {
ch := make(chan int)
go test(ch)
for {
val, ok := <-ch
if ok == false {// Ok is false, there is no data to read
break // Break the loop
}
fmt.Println("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
Copy the code
Use the select
Example chan_select. Go
package main
import "fmt"
// https://go.dev/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:
fmt.Println("quit")
return}}}func main(a) {
ch := make(chan int)
quit := make(chan int)
go func(a) {
for i := 0; i < 10; i++ {
fmt.Println(<-ch)
}
quit <- 0
}()
fibonacci(ch, quit)
}
// Run output:
/ / 0
/ / 1
/ / 1
/ / 2
/ / 3
/ / 5
/ / 8
/ / 13
/ / 21
/ / 34
// quit
Copy the code
Some usage scenarios for channels
1. As the data transmission pipeline of Goroutine
package main
import "fmt"
// https://go.dev/tour/concurrency/2
func sums(s []int, c chan int) {
sum := 0
for _, v := range s {
sum += v
}
c <- sum
}
func main(a) {
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
fmt.Println(x, y, x+y)
}
Copy the code
Batch sum with Goroutine and channel
2. Synchronized channels
A channel without buffers can act as a data synchronization channel.
For a channel with no buffer, the sending goroutine and the receiving goroutine need to be ready at the same time. That is, the sending and receiving goroutine need to be paired in order to complete the sending and receiving operations.
If the goroutines of both parties are not ready at the same time, a channel causes the goroutine that performed the send or receive first to block and wait. This is where a channel without buffers is used for data synchronization.
An example from gobyexample:
package main
import (
"fmt"
"time"
)
//https://gobyexample.com/channel-synchronization
func worker(done chan bool) {
fmt.Println("working...")
time.Sleep(time.Second)
fmt.Println("done")
done <- true
}
func main(a) {
done := make(chan bool.1)
go worker(done)
<-done
}
Copy the code
Note: Synchronous channels should never send and receive data in the same Goroutine coroutine. A deadlock may result.
3. Asynchronous channels
A buffered channel can be used as an asynchronous channel.
Buffered channels also have operational considerations:
If there are no values in the channel, the channel is empty, and the receiver is blocked.
If the buffer in a channel is full, the sender is blocked.
Note: A channel with a buffer must be closed if it is used up, otherwise the goroutine handling the channel will block and form a deadlock.
package main
import (
"fmt"
)
func main(a) {
ch := make(chan int.4)
quitChan := make(chan bool)
go func(a) {
for v := range ch {
fmt.Println(v)
}
quitChan <- true // Channel is used to indicate that the program has finished executing
}()
ch <- 1
ch <- 2
ch <- 3
ch <- 4
ch <- 5
close(ch) // Close the channel
<-quitChan // Unblock after receiving a channel notification. This is also a usage of channel
}
Copy the code
4. Channel timeout processing
Channel implements timeout handling in combination with time.
When a channel reads data for more than a certain amount of time and no data arrives, it is notified of the timeout, preventing the current Goroutine from being blocked.
chan_timeout.go
package main
import (
"fmt"
"time"
)
func main(a) {
ch := make(chan int)
quitChan := make(chan bool)
go func(a) {
for {
select {
case v := <-ch:
fmt.Println(v)
case <-time.After(time.Second * time.Duration(3)):
quitChan <- true
fmt.Println("timeout, send notice")
return}}} ()for i := 0; i < 4; i++ {
ch <- i
}
<-quitChan // The output value is equivalent to receiving a notification to unblock the main procedure
fmt.Println("main quit out")}Copy the code
Considerations for using channels and deadlock analysis
Uninitialized channel read/write close operation
1. Read: An uninitialized channel that causes deadlock deadlock when reading data inside
var ch chan int
<-ch // Uninitialized channel read data will deadlock
Copy the code
2. Write: An uninitialized channel that causes deadlock deadlock when writing data to it
var ch chan int
ch<- // Uninitialized channel write data will deadlock
Copy the code
3. Close: An uninitialized channel. Closing the channel causes panic
var ch chan int
close(ch) // Close the uninitialized channel, triggering panic
Copy the code
Initialization of a channel read/write shutdown operation
1. An initialized channel with no buffer
// Snippet 1
func main(a) {
ch := make(chan int)
ch <- 4
}
Copy the code
Snippet 1: No buffered channel, and only writes without reads, causes a deadlock
// Snippet 2
func main(a) {
ch := make(chan int)
val, ok := <-ch
}
Copy the code
Snippet 2: No buffered channel, and only read without write, causes a deadlock
// Snippet 3
func main(a) {
ch := make(chan int)
val, ok := <-ch
if ok {
fmt.Println(val)
}
ch <- 10 // Write here. But there's already a deadlock ahead
}
Copy the code
Snippet 3: There is no buffered channel, both write and read, but a deadlock has occurred at val, ok := <-c. The following code cannot execute.
// Snippet 4
func main(a) {
ch := make(chan int)
ch <- 10
go readChan(ch)
time.Sleep(time.Second * 2)}func readChan(ch chan int) {
for {
val, ok := <-ch
fmt.Println("read ch: ", val)
if! ok {break}}}Copy the code
Fatal error: All goroutines are asleep – deadlock! .
This is because the code that writes data to channle ch < -10, where the data is already deadlocked. Switch ch<-10 with Go readChan(ch) and the program will work without deadlock.
// Snippet 5
func main(a) {
ch := make(chan int)
go writeChan(ch)
for {
val, ok := <-ch
fmt.Println("read ch: ", val)
if! ok {break
}
}
time.Sleep(time.Second)
fmt.Println("end")}func writeChan(ch chan int) {
for i := 0; i < 4; i++ {
ch <- i
}
}
Copy the code
Snippet 5: A channel without buffering is both written and read. Unlike the previous snippets, not one data is written to a channel.
Think about it, does this program cause deadlocks? Think about it for 10 seconds. Don’t read it yet.
Fatal error: All goroutines are ASLEEP – deadlock! .
Why is that? This program fragment has both read and write and opens a Goroutine to write data first. Why is it deadlocked?
The reason is the for loop in main(). You might ask, doesn’t a break break the for loop? The code is written, but the program is not executed here.
Val, ok := <-ch, ok is always true, because there is nothing in the program to close the channel. You can print this OK value and see if it’s always true. When the for loop finishes reading the value in the channel, the program runs again to val, ok := <-ch, and the deadlock occurs because there is no data in the channel.
Close the channel in writeChan and add close(ch). Tell for I’m done and close channel.
Run the program with the channel code closed:
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
Copy the code
The program output results normally.
Unbuffered channel deadlocks unbuffered channel deadlocks
- A channel is initialized with make
- Reads and writes must be paired and not in the same Goroutine
- Always start a coroutine with GO to perform a read or write operation
- Write multiple times, for reads data, the writer is careful to close channel(snippet 5)
2. Initialized channel with buffer
// Snippet 1
func main(a) {
ch := make(chan int.1)
val, ok := <-ch
}
Copy the code
Snippet 1: There is a buffered channel that reads data first, which blocks all the time, causing a deadlock.
// Snippet 2
func main(a) {
ch := make(chan int.1)
ch <- 10
}
Copy the code
Code snippet 2: Same as code snippet 1, there is a buffer channel, only write but not read, also blocked, resulting in deadlock.
// Snippet 3
func main(a) {
ch := make(chan int.1)
ch <- 10
val, ok := <-ch
if ok {
fmt.Println(val, ok)
}
}
Copy the code
Snippet 3: Buffered channel, read and write, normal output.
Channel with buffer
- If the channel is full, the sender will block
- If channle is empty, the receiver will block
- If in the same Goroutine, the write operation must precede the read operation
reference
- Go. Dev/tour/concur…
- Go. Dev/ref/spec # Ch…
- Go. Dev/ref/spec # Se…
- Go. Dev/ref/spec # Re…
- Go. Dev/ref/spec # Cl…
- Go. Dev/doc/effecti…
- Go. Dev/ref/spec # Se…
- gobyexample.com/
- Concurrency is not parallelism – The Go Programming Language