Go language (basic)-channel

1 Introduction

  • Channel is a very important concept in Go. Cooperating with goroutine is the key to Go's convenient implementation of concurrent programming. There are two mechanisms to achieve concurrency: the first is the message passing mechanism, the second is the shared memory mechanism, and the channel in Go uses the shared memory mechanism to cooperate with goroutine to achieve concurrency.
  • This post introduces channels in Go from the shallower to the deeper, so that you can review them later.

2. The use of channel

2.1. Declare and create channel

2.1.1, two-way channel
  • The so-called two-way channel means that you can write data to the channel and read data from the channel. The corresponding one is a one-way channel.
  • Declare channel
var ch chan dataType
  • The dataType is very broad, it can be a basic data type, it can also be a map, slice, custom struct type, or even a channel. So it is easy to share a variety of data through channels in Go.
  • Create channel There are two ways, the first is an unbuffered channel, and the second is a buffered channel. The creation method is as follows:
//方式一:无缓冲
nobufferedch := make(chan int)
//方式二:带缓冲
bufferedch := make(chan int, 5)
2.1.2, one-way channel
  • The so-called one-way channel means that data can only be written to the channel, or data can only be read from the channel.
  • Declare channel
//第一种:send-only channel
var singledirectch chan<- dataType
//第二种:receive-only channel
var singledirectch <-chan dataType
  • Create channel There are also two ways, the first is an unbuffered channel, and the second is a buffered channel. The creation method is as follows (take send-only channel as an example):
//方式一:无缓冲
nobufferedch := make(chan<- int)
//方式二:带缓冲
bufferedch := make(chan<- int, 5)
  • When we use make to create a channel, what actually returns is a pointer to the channel.

2.2. Send and receive data on the channel (introductory case)

  • A channel is like a message pipeline. The sender sends a message on end a of the pipeline, and the receiver receives the message on end b of the pipeline. When the message pipeline is full of messages, the sender cannot send messages into it and has to stop. , Only when the receiver fetches the message from the message pipeline and the pipeline is no longer full, the sender that stopped before can continue to send messages to the pipeline; similarly, when there is no message in the pipeline, the receiver has to stop. Stop, wait for the sender to send the message to the pipe, the receiver can continue to receive the message in the pipe.
  • Next with a classicProducer-consumerModel, which introduces sending and receiving data on the channel.
  • Here, the bicycle manufacturer Producer is the sender of the message, the consumer who bought the bicycle is the receiver of the message, and the bicycle store Store is the message pipeline, and then this is a buffered message pipeline with a buffer capacity of 5, that is, this A maximum of 5 bicycles can be placed in the store at a time. When there are 5 bicycles in the store, the Producer will stop for a rest. Not much to say, just go to the code.
package main

import (
	"fmt"
	"time"
)

type Bike struct {
    
    
	Id uint32
	Brand string
	Location string
}

var Store chan *Bike
var IsFinish chan bool

func init() {
    
     //初始化函数,先于 main 函数执行
	Store = make(chan *Bike,5)
	IsFinish = make(chan bool)
}

func Producer() {
    
     //自行车生产者
	for i := 0; i < 10; i++ {
    
     //总共生产 10 辆自行车
		bike := &Bike{
    
    
			Id:       uint32(i),
			Brand:    "凤凰牌",
			Location: "上海",
		}
		Store <- bike
		fmt.Printf("**%s生产者**:%d号%s牌自行车\n", (*bike).Location, (*bike).Id, (*bike).Brand)
		time.Sleep(time.Second * 2) //每隔两秒钟生产一辆自行车
	}
	//生产完之后,要关闭 Store,等到 Store 里的自行车被消费者买完以后,消费者会通过知道 Store 已经关闭了,而不再继续等待买 Store 里的自行车
	close(Store) //close 是非常有必要的!
}

func Consumer() {
    
     //自行车消费者
	for {
    
    
		bike, ok := <- Store
		if bike == nil && ok == false {
    
    
			break
		}
		fmt.Printf("==%s消费者==:%d号%s牌自行车\n", (*bike).Location, (*bike).Id, (*bike).Brand)
		time.Sleep(time.Second * 3) //消费者每隔 3 秒钟买一辆自行车
	}
	IsFinish <- true //告诉主线程 Store 里的自行车已经卖完了,生产者也不生产了,可以结束了
}

func main() {
    
    
	go Producer()
	go Consumer()
	<- IsFinish //在 IsFinish <- true 这句代码没执行前,主线程会阻塞在这里,这么做的目的就是防止上面两个协程还没执行完,主线程就提前退出了
}
  • The execution results are as follows:
**上海生产者**:0号凤凰牌牌自行车
==上海消费者==:0号凤凰牌牌自行车
**上海生产者**:1号凤凰牌牌自行车
==上海消费者==:1号凤凰牌牌自行车
**上海生产者**:2号凤凰牌牌自行车
==上海消费者==:2号凤凰牌牌自行车
**上海生产者**:3号凤凰牌牌自行车
**上海生产者**:4号凤凰牌牌自行车
==上海消费者==:3号凤凰牌牌自行车
**上海生产者**:5号凤凰牌牌自行车
==上海消费者==:4号凤凰牌牌自行车
**上海生产者**:6号凤凰牌牌自行车
**上海生产者**:7号凤凰牌牌自行车
==上海消费者==:5号凤凰牌牌自行车
**上海生产者**:8号凤凰牌牌自行车
==上海消费者==:6号凤凰牌牌自行车
**上海生产者**:9号凤凰牌牌自行车
==上海消费者==:7号凤凰牌牌自行车
==上海消费者==:8号凤凰牌牌自行车
==上海消费者==:9号凤凰牌牌自行车

2.3, the combined use of channel and select

2.3.1、Introduction case
  • select is used for multiple channels to monitor and send and receive messages. When any case meets the conditions, it will be executed. If there is no executable case, the default will be executed. If there is no default, the program will be blocked.
func SelectSample() {
    
    
	c1 := make(chan string)
	go func(c chan string) {
    
    
		time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
		c <- "协程一来也!"
	}(c1)
	c2 := make(chan string)
	go func(c chan string) {
    
    
		time.Sleep(time.Duration(rand.Intn(10)) * time.Second)
		c <- "协程二来也!"
	}(c2)

	for {
    
    
		select {
    
    
		case v, ok := <-c1:
			fmt.Println(v, ok)
		case v, ok := <- c2:
			fmt.Println(v, ok)
		default:
			fmt.Println("waiting")
		}
		time.Sleep(time.Second)
	}
}
  • Call the SelectSample function in the main function, the execution result is as follows:
waiting
协程一来也! true
waiting
waiting
waiting
waiting
waiting
协程二来也! true
waiting
waiting
waiting
......
2.3.2, timeout processing
  • In many cases, we use select to monitor the channel, and then make the next step according to the state of the channel, and we often use default to cooperate with the operation. The use of default is to execute the statement following default when all cases do not match, but There is a situation that although the currently monitored channel status has not changed (the channel has not written data or read data), the channel status may change after a period of time, so at this time, we need to give the program something Waiting time, instead of immediately executing the statement following default. Speaking of this, some people may think that since it is waiting for a period of time, then I can use time.Sleep(time.Duration). It is true to think so, but there is a question, how long do you want the program to sleep for? Well, you are not sure how long to put the program to sleep (wait). So now I will introduce another method: time.After(time.Duration), this function execution returns a channel, here might as well let timeout := time.After(time.Duration), the function of this function is time. After Duration, execute and return to timeout. It is a channel. Since it is a channel, then we can put it after the case condition and "compete" with other channels. Not much to say, the code:
func Timeout1() {
    
    
	c := make(chan string, 1)
	finish := make(chan bool)
	go func() {
    
    
		time.Sleep(time.Second * 3)
		c <- "message"
	}()

	go func() {
    
    
		for {
    
    
			select {
    
    
			case res := <-c:
				fmt.Println(res, time.Now())
				finish <- true
				break
			case val, ok := <-time.After(time.Second * 2):
				fmt.Println("timeout", val, ok)
			}
		}
	}()
	<- finish
}
  • Put the above code into main and execute it, and the output result is as follows. Through the output result, it is found that the output time interval before and after the two statements is 1 second
timeout 2021-03-26 20:55:44.47262 +0800 CST m=+2.007340901
message 2021-03-26 20:55:45.4725173 +0800 CST m=+3.007238201

3. Points to note

3.1, use for-range iterative output channel

  • When using for-range to iteratively output the data in the channel, you must explicitly close the channel at a certain position, otherwise you will encounter a deadlock situation. Use a piece of code to demonstrate:
func DeadLockSample() {
    
    
	c := make(chan int)
	go func(c chan int) {
    
    
		fmt.Println("in goroutine")
		c <- 100
		//close(c)
	}(c)
	for {
    
    
		val, ok := <-c
		fmt.Println(val, ok)
	}
}
  • The output result of the above code executed in the main function is as follows:
in goroutine
100 true
fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
goStudy/self/Channel.DeadLockSample()
	D:/program/Code/go/src/goStudy/self/Channel/test_channel.go:125 +0x7b
main.main()
	D:/program/Code/go/src/goStudy/self/main.go:56 +0x27

Process finished with exit code 2
  • So if the comment symbol in front of close© above is canceled, what will be the execution result? Please see the execution result below:
in goroutine
100 true
0 false
0 false
0 false
0 false
......

3.2 Precautions for time.After()

  • The time.After() function will re-timing every time it is selected. More generally speaking, time.After() will restart the timing every time it is executed. For example, the timeout period I set is 3 seconds. , There is a task that takes 2 seconds to complete once, and after the execution is completed, write data to a channel (work), and then this task will be executed 5 times in a loop. Now, now time.After and ch are both waiting behind the select case condition . We assume that the time point when the program starts execution is 0 seconds, then at the second second, the task is successfully executed once, at the fourth second, the task is executed smoothly again, until the 10th second, the task is executed. The overtime tasks are not triggered and executed once, no explanation, just go to the code:
func Timeout4() {
    
    
	work := make(chan string)
	finish := make(chan bool)
	go func() {
    
    
		for i := 0; i < 5; i++ {
    
    
			time.Sleep(time.Second * 1)
			work <- "任务" + strconv.Itoa(i)
		}
	}()
	go func() {
    
    
		for {
    
    
			select {
    
    
			case val := <- work:
				fmt.Println(val)
				if strings.HasSuffix(val, "4") {
    
    
					finish <- true
				}
			case <-time.After(time.Second * 3):
				fmt.Println("超时了...")
			}
		}
	}()
	<- finish
}
  • Put the above code into main for execution, and the execution result is as follows:
任务0
任务1
任务2
任务3
任务4

Guess you like

Origin blog.csdn.net/wxy_csdn_world/article/details/115218290