26

Go: Asynchronous Preemption - A Journey With Go - Medium

 4 years ago
source link: https://medium.com/a-journey-with-go/go-asynchronous-preemption-b5194227371c
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Go: Asynchronous Preemption

Image for post
Image for post

Illustration created for “A Journey With Go”, made from the original Go Gopher, created by Renee French.

ℹ️ This article is based on Go 1.14.

The preemption is an important part of the scheduler that lets it distribute the running time among the goroutines. Indeed, without preemption, a long-running goroutine that hogs the CPU would block the other goroutines from being scheduled. The version 1.14 introduces a new technique of asynchronous preemption, giving more power and control to the scheduler.

For more details about the previous behavior and its drawback, I suggest you read my article “Go: Goroutine and Preemption.”

Workflow

Let’s start with an example where preemption is needed. Here is a code where many goroutines loop for a while without any function call, meaning no opportunity for the scheduler to preempt them:

Image for post
Image for post

However, when visualizing the traces from this program, we clearly see the goroutines are preempted and switch among them:

Image for post
Image for post

We can also see that all the blocks representing the goroutines have the same length. The goroutines get almost the same running time (around 10/20ms):

Image for post
Image for post

The asynchronous preemption is triggered based on a time condition. When a goroutine is running for more than 10ms, Go will try to preempt it.

The preemption is initiated by the thread sysmon, dedicated to watching after the runtime, long-running goroutines included. Once a goroutine is detected running more than 10ms, a signal is emitted to the current thread for its preemption:

Image for post
Image for post

Then, once the message is received by the signal handler, the thread is interrupted to handle it, and therefore does not run the current goroutine anymore — G7 in our example. Instead, gsignal is scheduled to manage the incoming signal. Since it finds out it is a preemption instruction, it sets instruction up to stop the current goroutine when the program resumes after the signal handling. Here is a diagram of this second phase:

Image for post
Image for post

For more details about gsignal, I suggest you read my article “Go: gsignal, Master of Signals.”

Implementation

The first detail of the implementation we have seen is the chosen signal SIGURG. This choice is well explained in the proposal “Proposal: Non-cooperative goroutine preemption”:

- It should be a signal that’s passed-through by debuggers by default.
- It shouldn’t be used internally by libc in mixed Go/C binaries […].
- It should be a signal that can happen spuriously without consequences.
- We need to deal with platforms without real-time signals […].

Then, once the signal is injected and received, Go needs a way to stop the current goroutine when the program resumes. To achieve this, Go will push an instruction in the program counter, to make it looks like the running program called a function in the runtime. This function parks the goroutine and hands it to the scheduler that will run another one.

We should note that Go cannot stop the program anywhere; the current instruction must be a safe point. For instance, if the program is currently calling the runtime, it would not be safe to preempt the goroutine since many functions in the runtime should not be preempted.

This new preemption also benefits the garbage collector that can stop all the goroutines in a more efficient way. Indeed, stopping the world is now much easier, Go just has to send a signal to every running thread. Here is an example when the garbage collector is running:

Image for post
Image for post

Then, each thread receives the signal and pauses the execution until the garbage collector starts the world again.

For more information about the phase “Stop the World,” I suggest you read my article “Go: How Does Go Stop the World?

At last, this feature is shipped with a flag to deactivate the asynchronous preemption. You can run your program with GODEBUG=asyncpreemptoff=1, that will allow you to debug your program if you see anything incorrect due to the upgrade to Go 1.14, or see how your application performs with or without asynchronous preemption.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK