This is the 22nd post in the “Learn to Go” series

The previous article covered some of the uses of coroutines, such as how to create coroutines, anonymous coroutines, and so on. In this article we will talk about channels. A channel is a communication channel between coroutines, sending data from one end and receiving data from the other.

Channel statement

Before using a channel, you need to declare it in two ways:

var c chan int  		/ / way
c := make(chan int)		2 / / way
Copy the code

The channel is created using the keyword chan, which has a type when declared, indicating that the channel allows only that type of data to be transmitted. The zero value of the channel is nil. Method one declares the nil channel. Nil channels are useless because they can neither send nor receive data. The second method uses the make function to create an available channel C.

func main(a) {
	c := make(chan int)
	fmt.Printf("c Type is %T\n",c)
	fmt.Printf("c Value is %v\n",c)
}
Copy the code

Output:

c Type is chan int
c Value is 0xc000060060
Copy the code

The above code creates channel C and allows only int data to be transmitted. Generally, the channel is passed to the function or method as a parameter to realize the communication between two coroutines. Have you noticed that the value of channel C is an address, and the value of C can be used directly when passing the parameter without addressing.

Use of channels

Read and write data

Go provides syntax for manipulating channels:

c := make(chan int)
/ / write data
c <- data   

/ / read the data
variable <- c  / / way
<- c  			2 / / way
Copy the code

Note the position of the channel. On the left side of the arrow is write data, and on the right side is read data from the channel. The above method is reasonable, and the read data is discarded. Note: channel operations are blocked by default, and writing data into a channel blocks the current coroutine until it is read out by another coroutine. After a coroutine is blocked by a channel operation, the Go scheduler calls other available coroutines so that the program does not block all the time. This feature of the channel is very useful, as we will see. Let’s review an example from the previous article:

func printHello(a) {
	fmt.Println("hello world goroutine")}func main(a) {
	go printHello()
	time.Sleep(1*time.Second)
	fmt.Println("main goroutine")}Copy the code

The example main() coroutine uses the time.sleep () function to Sleep for 1s while printHello() executes. It’s very dark technology, and it should never be used in a production environment. Let’s use channel to modify:

func printHello(c chan bool) {
	fmt.Println("hello world goroutine")
	<- c    // Read channel data
}

func main(a) {
	c := make(chan bool)
	go printHello(c)
	c <- true    // main coroutine blocks
	fmt.Println("main goroutine")}Copy the code

Output:

hello world goroutine
main goroutine
Copy the code

In the example above, after the main coroutine creates the printHello coroutine, line 8 writes data to channel C. The Main coroutine blocks. The Go scheduler can use the printHello coroutine to read data from channel C. Note: The read operation is not blocking because channel C already has readable data, otherwise the read operation would block.

A deadlock

As mentioned earlier, the channel blocks while reading/writing data, and the scheduler schedules other available coroutines. So the question is, what happens if there are no other coroutines available? That’s right, the famous deadlock happens. In the simplest case, you just write to the channel.

func main(a) {
	c := make(chan bool)
	c <- true    // Write without reading
	fmt.Println("main goroutine")}Copy the code

Error:

fatal error: all goroutines are asleep - deadlock!
Copy the code

The same error occurs when you read only and do not write.

Close the channel and for loop

The channel that sends the data has the ability to choose to close the channel and the data cannot be transmitted. When data is received, a state can be returned to determine whether the channel is closed:

val, ok := <- channel
Copy the code

Val is the received value, and OK identifies whether the channel is closed. If true, the channel can also perform read and write operations. False indicates that the channel is closed and data cannot be transmitted. Use the built-in function close() to close the channel.

func printNums(ch chan int) {
	for i := 0; i < 4; i++ {
		ch <- i
	}
	close(ch)
}

func main(a) {
	ch := make(chan int)
	go printNums(ch)
	for {
		v, ok := <-ch
		if ok == false {     // Check whether the channel is closed by ok
			fmt.Println(v, ok)
			break
		}
		fmt.Println(v, ok)
	}
}
Copy the code

Output:

0 true
1 true
2 true
3 true
0 false
Copy the code

PrintNums coroutine closes the channel after writing data, and checks ok in the main coroutine. If false, the channel is closed and exits for loop. The value read from the closed channel is zero of the corresponding type, and the output value of the last line above is zero of type int.

To use the for loop, you need to manually check whether the channel is closed. If you’re bored, use for range to read the channel. When the channel closes, for range automatically exits.

func printNums(ch chan int) {
	for i := 0; i < 4; i++ {
		ch <- i
	}
	close(ch)
}

func main(a) {
	ch := make(chan int)
	go printNums(ch)

	for v := range ch {
		fmt.Println(v)
	}
}
Copy the code

Output:

0
1
2
3
Copy the code

Note that a channel must be closed () after sending a for range, or a deadlock will occur.

Buffering channel and channel capacity

The channel created earlier is unbuffered and the read and write channel blocks the current coroutine immediately. For buffered channels, writes do not block the current channel until the channel is full, and reads do not block the current channel unless there is no data in the channel. Create buffered channel:

ch := make(chan type, capacity)  
Copy the code

Capacity is the buffer size and must be greater than 0. The built-in functions len() and cap() can calculate the length and capacity of the channel.

func main(a) {
	ch := make(chan int.3)

	ch <- 7
	ch <- 8
	ch <- 9
	//ch <- 10    
	// If the comment is open, the coroutine blocks and a deadlock occursA deadlock occurs: channels are full and no other channels are available to read data fmt.println ("main stopped")}Copy the code

Output: main stopped creates channels with buffers of 3 that will not block when writing 3 data. If the comment on line 7 is turned on, the channel is full, the coroutine is blocked, and there are no other coroutines available to read data, a deadlock occurs. Here’s another example:

func printNums(ch chan int) {

	ch <- 7
	ch <- 8
	ch <- 9
	fmt.Printf("channel len:%d,capacity:%d\n".len(ch),cap(ch))
	fmt.Println("blocking...")
	ch <- 10   / / blocking
	close(ch)
}

func main(a) {
	ch := make(chan int.3)
	go printNums(ch)

	/ / sleep 2 s
	time.Sleep(2*time.Second)
	for v := range ch {
		fmt.Println(v)
	}

	fmt.Println("main stopped")}Copy the code

Output:

channel len:3,capacity:3
blocking...
7
8
9
10
main stopped
Copy the code

The purpose of hibernation 2s is to block the channel full of data, as you can see from the printed results. After 2s, the main coroutine reads data from the channel, and when the channel capacity is surplus, the block is released and the data continues to be written.

If the buffer channel is closed but has data, data can still be read:

func main(a) {
	ch := make(chan int.3)
	
	ch <- 7
	ch <- 8
	//ch <- 9
	close(ch)

	for v := range ch {
		fmt.Println(v)
	}

	fmt.Println("main stopped")}Copy the code

Output:

7
8
main stopped
Copy the code

A one-way channel

All the previous ones were two-way channels that could both send and receive data. We can also create one-way channels that send or receive only data. Grammar:

sch := make(chan<- int)
rch := make(< -chan int) 
Copy the code

SCH is sending channel only, and RCH is receiving channel only. What’s the use of this one-way channel? Well, we can’t just send and miss or miss. This channel is mainly used when the channel is transmitted as a parameter. Go provides automatic conversion, two-way to one-way. Rewrite the previous example:

func printNums(ch chan<- int) {
	for i := 0; i < 4; i++ {
		ch <- i
	}
	close(ch)
}

func main(a) {
	ch := make(chan int)
	go printNums(ch)

	for v := range ch {
		fmt.Println(v)
	}
}
Copy the code

Output:

0
1
2
3
Copy the code

In the main coroutine, CH is a two-way channel. PrintNums () automatically converts CH into a one-way channel when receiving parameters, and only sends and does not receive. But in the main coroutine, CH can still receive data. The main reason for using one-way channels is to improve the type safety of your program and make it less error-prone.

Channel data type

A channel is a class of values like int, string, etc., that can be used anywhere like any other value, such as as a structure member, a function parameter, a function return value, or even as the type of another channel. Let’s look at data types that use channels as another channel.

func printWord(ch chan string) {
	fmt.Println("Hello " + <-ch)
}

func productCh(ch chan chan string)  {
	c := make(chan string)   // Create a string type channel
	ch <- c     // Transmission channel
}

func main(a) {

	// Create a channel of type chan string
	ch := make(chan chan string)
	go productCh(ch)
	// c is the channel of string type
	c := <-ch
	go printWord(c)
	c <- "world"
	fmt.Println("main stopped")}Copy the code

Output:

Hello world
main stopped
Copy the code

Hope this article helps you, Good Day!


Original article, if need to be reproduced, please indicate the source! Check out “Golang is coming” or go to seekload.net for more great articles.

Prepare for you to learn Go language related books, public number background reply [ebook] receive!