goroutine's multi-core parallelization gives up time slices

1. Multi core parallel Starting from Go 1.5, ...
1. Multi core parallel
2. Give up the time slice

1. Multi core parallel

Starting from Go 1.5, Go's GOMAXPROCS default value has been set to the number of CPU cores, which allows our Go program to make full use of each CPU of the machine and improve the concurrency performance of our program to the greatest extent.

runtime.GOMAXPROCS(16)

How many CPU cores should be set? In fact, another function NumCPU() is provided in the runtime package to get
Take the number of cores. It can be seen that the Go language has actually sensed all the ring 􏲁 information. In the next version, we can use this information to schedule goroutine to all CPU cores, so as to maximize the multi-core computing power of the server. It is only a matter of time before GOMAXPROCS is abandoned.

fmt.Printf("runtime.NumCPU(): %v\n", runtime.NumCPU()) //runtime.NumCPU(): 12

Look at the following simple code

package main import ( "fmt" "sync" ) var wg sync.WaitGroup func myPrintA() { defer wg.Done() fmt.Println("A") } func myPrintB() { defer wg.Done() fmt.Println("B") } func main() { for i := 1; i <= 10; i++ { wg.Add(2) go myPrintA() go myPrintB() } wg.Wait() }

It is found that the outputs of a and B are irregular and random
Operation results:

[Running] go run "/Users/codehope/Study/time-to-go/Code/re.channel/cpu_mult_calc.go" A B A A B A B A B A B B B A A B A A B B [Done] exited with code=0 in 0.364 seconds

It is proved that the two Go processes opened in each cycle are parallel, because in my version, the default value of GOMAXPROCS of the default Go has been set to the number of cpu cores. If I set GOMAXPROCS of Go to 1, it means that these goroutines are running on a cpu core. When one goroutine gets a time slice for execution, other goroutines will be in a waiting state

package main import ( "fmt" "runtime" "sync" ) var wg sync.WaitGroup func myPrintA() { defer wg.Done() fmt.Println("A") } func myPrintB() { defer wg.Done() fmt.Println("B") } func main() { runtime.GOMAXPROCS(1) for i := 1; i <= 10; i++ { wg.Add(2) go myPrintA() go myPrintB() } wg.Wait() }

After setting runtime.GOMAXPROCS(1), run again. No matter how many times you run, there are two alternating outputs, which is very regular

[Running] go run "/Users/codehope/Study/time-to-go/Code/re.channel/cpu_mult_calc.go" B A B A B A B A B A B A B A B A B A B A [Done] exited with code=0 in 0.353 seconds

Because the go process created in each cycle is on the same cpu core, there is only one P queue corresponding to the GPM model, which needs to be queued for execution, so the above output results appear

2. Give up the time slice

We can control when to actively transfer time slices to other goroutines in each goroutine, which can be realized by using the Gosched() function in the runtime package.
In fact, if you want to control the behavior of goroutine more finely, you must have a deeper understanding of the specific functions provided by the runtime package in the Go language development package.

We still modify the above code: (similarly, when the GOMAXPROCS of go is 1, the goroutine p queue is 1, and multiple go processes cannot be parallel)

package main import ( "fmt" "runtime" "sync" ) var wg sync.WaitGroup func myPrintA() { defer wg.Done() fmt.Println("A") } func myPrintB() { defer wg.Done() runtime.Gosched() //Before printing B, give up the time slice occupied by the current goroutine fmt.Println("B") } func main() { runtime.GOMAXPROCS(1) for i := 1; i <= 10; i++ { wg.Add(2) go myPrintA() go myPrintB() } wg.Wait() }

See the above code. Before printing B, let's give up the time slice occupied by the current goroutine. What will be the output result?

[Running] go run "/Users/codehope/Study/time-to-go/Code/re.channel/tempCodeRunnerFile.go" A A A A A A A A A A B B B B B B B B B B [Done] exited with code=0 in 0.574 seconds

As you can see, print all A before printing B, because the two goroutines opened in each cycle are executed alternately. When the go collaboration of myPrintB grabs the time slice, give up the current goroutine and the time slice before executing fmt.Println("B"), save the current state, and continue to execute when the time slice is grabbed again, There may be A little doubt here (then why doesn't b of the current loop continue to execute? And what about all the output A executed first?) for this question, leave A guess first (after the current time slice is released, it is robbed by the goroutine of the next loop, if the current loop has enough time (if we don't go to the next loop so soon, we won't create A new goroutine), which may be executed in the current loop). Let's confirm our conjecture. We let the program sleep for 1s in each loop

package main import ( "fmt" "runtime" "sync" "time" ) var wg sync.WaitGroup func myPrintA() { defer wg.Done() fmt.Println("A") } func myPrintB() { defer wg.Done() runtime.Gosched() fmt.Println("B") } func main() { runtime.GOMAXPROCS(1) for i := 1; i <= 10; i++ { wg.Add(2) go myPrintA() go myPrintB() time.Sleep(time.Second) } wg.Wait() }

Output result: output a and B every other second. (after B gives up the time slice, he can grab the time slice again and continue to execute the following code, because there is enough time and idle time slice for him, so it will not be robbed by the goroutine created in the next cycle so soon!)

[Running] go run "/Users/codehope/Study/time-to-go/Code/re.channel/cpu_mult_calc.go" A B A B A B A B A B A B A B A B A B A B [Done] exited with code=0 in 10.569 seconds

28 October 2021, 22:27 | Views: 9967

Add new comment

For adding a comment, please log in
or create account

0 comments