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:
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):
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 chanboolfuncinit(){
//初始化函数,先于 main 函数执行
Store =make(chan*Bike,5)
IsFinish =make(chanbool)}funcProducer(){
//自行车生产者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 是非常有必要的!}funcConsumer(){
//自行车消费者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 里的自行车已经卖完了,生产者也不生产了,可以结束了}funcmain(){
goProducer()goConsumer()<- IsFinish //在 IsFinish <- true 这句代码没执行前,主线程会阻塞在这里,这么做的目的就是防止上面两个协程还没执行完,主线程就提前退出了}
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.
funcSelectSample(){
c1 :=make(chanstring)gofunc(c chanstring){
time.Sleep(time.Duration(rand.Intn(10))* time.Second)
c <-"协程一来也!"}(c1)
c2 :=make(chanstring)gofunc(c chanstring){
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:
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:
funcTimeout1(){
c :=make(chanstring,1)
finish :=make(chanbool)gofunc(){
time.Sleep(time.Second *3)
c <-"message"}()gofunc(){
for{
select{
case res :=<-c:
fmt.Println(res, time.Now())
finish <-truebreakcase 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
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:
funcDeadLockSample(){
c :=make(chanint)gofunc(c chanint){
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
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:
funcTimeout4(){
work :=make(chanstring)
finish :=make(chanbool)gofunc(){
for i :=0; i <5; i++{
time.Sleep(time.Second *1)
work <-"任务"+ strconv.Itoa(i)}}()gofunc(){
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: