Requirements: Simulate two coroutines and print letters A and B in a loop respectively.
Analysis: To achieve alternating collaboration between two coroutines, a channel communication mechanism must be used, and the channel happens to be blocked synchronously.
Half open method
First, we use a channel variable to control the alternating printing of two goroutines:
func main() { exit := make(chan bool) ch1 := make(chan int) go func() { for i := 1; i <= 10; i++ { ch1 <- 0 //Production ("A",i) } exit <- true }() go func() { for i := 1; i <= 10; i++ { <-ch1 //Consumption ("B",i) } }() <-exit }
It turned out that the effect of ABBAABBA... was printed.
That is, we control the order of the beginning, but do not control the order of the end, and concurrent unsafe situation occurs.
In fact, the semi-open mode can also be used in certain scenarios, such as: two goroutines, under conditional control, alternately print odd and even numbers:
func main() { exit := make(chan bool) ch1 := make(chan int) go func() { for i := 1; i <= 10; i++ { ch1 <- 0 if i%2 == 0 { ("A", i) } } exit <- true }() go func() { for i := 1; i <= 10; i++ { <-ch1 if i%2 == 1 { ("B", i) } } }() <-exit }
Closed way
Next we use two channel variables to simulate the mutex problem of goroutine loop body.
func main() { exit := make(chan bool) ch1, ch2 := make(chan bool), make(chan bool) go func() { for i := 1; i <= 10; i++ { ch1 <- true ("A", i) //The blocking is exclusive between ch1 and ch2 <-ch2 } exit <- true }() go func() { for i := 1; i <= 10; i++ { <-ch1 ("B", i) ch2 <- true } }() <-exit }
We use blocking exclusive mode at the beginning and end of the loop body, and the two chans alternately release control, achieving safe coroutine interactive control.
Let’s take a look at the following demo, the same principle:
func main(){ ch1 :=make(chan int) ch2 :=make(chan string) str :=[5]string{"a","b","c","d","e"} go func() { for i:=0;i<5;i++{ ch1<-i (i+1) <-ch2 } }() for _,v :=range str{ <-ch1 (v) ch2<-v } }
Buffer mode
The buffering mode is similar to the closed mode, except that in the closed mode, the two goroutines have clear head and tail roles. The first producer of buffer mode is handed over to the main coroutine, and the two goroutine structures are the same, and they swap roles in a wheel.
func main() { exit := make(chan bool) ch1, ch2 := make(chan bool,1), make(chan bool) ch1 <- true //Production (select a start item) go func() { for i := 1; i <= 10; i++ { if ok := <-ch1; ok { //Consumption ("A", 2*i-1) ch2 <- true //Production } } }() go func() { defer func() { close(exit) }() for i := 1; i <= 10; i++ { if ok := <-ch2; ok { //Consumption ("B", 2*i) ch1 <- true //Production } } }() <-exit }
in conclusion:
The essence of Channel is a synchronous production and consumption model
Supplement: go Let N coroutines print 1-100 alternately
Today I encountered an interview question, open N coroutines, and print 1-100 alternately. If given N=3, the output is:
goroutine0: 0
goroutine1: 1
goroutine2: 2
goroutine0: 3
goroutine1: 4
There was no answer during the interview. Although I later studied and referred to some online methods, I recorded them and uploaded the code first.
func print() { chanNum := 3 // chan quantity chanQueue := make([]chan int, chanNum) // Create chan Slice var result = 0 // Value exitChan := make(chan bool) // Exit logo for i := 0; i < chanNum; i++ { // Create chan chanQueue[i] = make(chan int) if i == chanNum-1 { // Write a piece of data to the last chan, and output from the first chan for the first output go func(i int) { chanQueue[i] <- 1 }(i) } } for i := 0; i < chanNum; i++ { var lastChan chan int // The previous goroutine can be output before output controls the output order var curChan chan int // The goroutine of the current blocking output if i == 0 { lastChan = chanQueue[chanNum-1] } else { lastChan = chanQueue[i-1] } curChan = chanQueue[i] go func(i int, lastChan, curChan chan int) { for { if result > 100 { // Exit if it exceeds 100 exitChan <- true } // Block until the previous output is finished, control the order <-lastChan ("thread%d: %d \n", i, result) result = result + 1 // The current goroutine has been output curChan <- 1 } }(i, lastChan, curChan) } <-exitChan ("done") }
1. The first for loop creates chan
2. The lastChan in the second for loop means that if the current chan wants to print data, it must be printed after the previous chan can be printed.
Here, assume that N=2, the chan index is 0 and 1. When index 1 is to be output, it will block until the chan of index 0 has data. After printing it, send a 1 to your chan to unblock it.
There is a special place here. When the index is 0, its dependency index chan is the length of chanQueue -1. If there is no such string of code in Chan, it will cause a deadlock.
if i == chanNum-1 { // Write a piece of data to the last chan, and output from the first chan for the first output go func(i int) { chanQueue[i] <- 1 }(i) }
The above is personal experience. I hope you can give you a reference and I hope you can support me more. If there are any mistakes or no complete considerations, I would like to give you advice.