Coroutine execution in Go actually does not have a strict order by default. Due to the design concept of the Go language GPM model, the M (machine) executor in GPM is actually the M (machine) executor in GPM, and our coroutine task G (goroutine) coroutine needs to be associated with a certain M by P (produce) before it can be executed. Each P has a private queue, and in addition, all Ps share a public queue. Therefore, when we create a coroutine, we do not execute immediately, but enter the queue and wait for allocation, and there is no sequential relationship between different queues.
But sometimes, we do not want all coroutines to be executed randomly, so we need to find a way to control the execution order of coroutines. Here we organize several methods to control the execution order of coroutines.
Cycle control
The idea is that we need to set a sequence number for each subcoroutine. Only after the current sequence number coroutine is executed can the next one be executed.
So we need a common variable to record the number of the coroutine that can be executed. At the same time, this variable must be thread-safe to ensure that every read and write operation of each coroutine is correct.
First, wait for the right time:
This function will continuously loop to get a count value. When the value of count is the same as i in the parameter, it will enter the function represented by the parameter fn and +1 of count.
Otherwise it will wait for one nanosecond and repeat the above steps.
var count uint32 func sequence(i uint32, fn func()) { for { //Use atomic operation if n := atomic.LoadUint32(&count); n == i { fn() atomic.AddUint32(&count, 1) break } () } }
Then use sequence to control the coroutine sequence:
We place the logic to be executed in the function fn and execute in the sequence function, and the function sequence ensures the execution order written.
Finally sequence(times, func() {}) is to make the main coroutine exit finally. Of course, we can use channel chan to implement it (refer to the previous article).
func main() { var times uint32 = 5 for i := uint32(0); i < times; i++ { go func(i uint32) { fn := func() { ("this i is %v\n", i) } sequence(i, fn) }(i) } //Let the main coroutine wait for the final execution sequence(times, func() {}) }
Execution results:
this i is 0
this i is 1
this i is 2
this i is 3
this i is 4
Channel control
The principle is that the front and back coroutines restrict each other through channels, and the latter coroutine tries to obtain the value in a channel. When there is no value in the channel, it will keep blocking.
The previous coroutine is responsible for closing the channel or sending values to the channel. If the current coroutine completes this operation, the latter coroutine can end the blocking and continue execution.
func main() { c1 := make(chan struct{}) c2 := make(chan struct{}) c3 := make(chan struct{}) go func() { //Coprocess 1 Unrestricted Direct execution Close channel 1 after execution is completed ("this value is 0") close(c1) }() go func() { //Coecho two needs to receive the value from channel one, or when the channel is closed, the result of the reception failure will be obtained, otherwise it will be blocked all the time //Close channel two after execution <-c1 ("this value is 1") close(c2) }() go func() { //Coecho three needs to receive the value from channel two, or when the channel is closed, the result of the reception failure will be obtained, otherwise it will be blocked all the time //Close channel three after execution <-c2 ("this value is 2") close(c3) }() //The main coroutine needs to receive the value from channel three, or when the channel is closed, the result of the reception failure will be obtained, otherwise it will be blocked all the time <-c3 }
Execution results
this value is 0
this value is 1
this value is 2
Mutex lock
Directly upload the code
func main() { times := 5 //Create an array of mutexes and use one for the main coroutine var cc = make([]*, times+1) //Put the mutex into the array, and the lock will be added directly by default for i := 0; i < len(cc); i++ { m := &{} () cc[i] = m } for i := 0; i < times; i++ { //Create child coroutine go func(index int) { //The subcoroutine tries to lock the lock corresponding to the index position in the array, and wait if it cannot obtain the lock //Because the initialized mutexes are already locked by default, the child coroutines created here will be blocked //Once the lock is obtained, the logic is executed, and the current index lock and index+1 lock are released, so that the child coroutine that is waiting for the lock at index +1 position can continue to execute cc[index].Lock() ("this value is %d \n", index) cc[index].Unlock() cc[index+1].Unlock() }(i) } //Unlock the lock with index position 0 so that the first child coroutine can continue to execute cc[0].Unlock() //Lock the lock for index and times. Only when the last child coroutine is executed will the lock be unlocked, and the main coroutine can continue to go down cc[times].Lock() cc[times].Unlock() }
This is the article about the detailed explanation of Golang's coroutine execution order method. For more relevant Go coroutine execution order content, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!