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:

  1. Read < – ch

  2. Write ch < –

  3. 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:

  1. If there are no values in the channel, the channel is empty, and the receiver is blocked.

  2. 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

  1. A channel is initialized with make
  2. Reads and writes must be paired and not in the same Goroutine
  3. Always start a coroutine with GO to perform a read or write operation
  4. 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

  1. If the channel is full, the sender will block
  2. If channle is empty, the receiver will block
  3. 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