In Go, we use go
to create a new goroutine. A goroutine is a lightweight thread managed by the Go runtime.
We are all familiar with the states of operating system threads. In The time in computers: how long will it take to switch the context?, we summarized the states of threads and the time it takes for thread scheduling.
This article was first published in the Medium MPP plan. If you are a Medium user, please follow me on Medium. Thank you very much.
Goroutines are similar; they have their own states, and their states are controlled by the runtime.
The data structure of goroutines is defined in runtime2.go. The g.atomicstatus represents a goroutine’s state. The source code also defines its range of values.
Apart from several unused states and states related to GC, a goroutine may be in one of the following nine states:
State | Description |
---|---|
_Gidle |
Just allocated and not yet initialized |
_Grunnable |
No code execution, no ownership of stack, stored in the run queue |
_Grunning |
Can execute code, owns a stack, assigned to kernel thread M and processor P |
_Gsyscall |
Executing a system call, owns a stack, not executing user code, assigned to kernel thread M but not in the run queue |
_Gwaiting |
Blocked due to runtime, not executing user code and not in the run queue, but may be in the waiting queue of a channel |
_Gdead |
Not in use, no code execution, may have allocated stack |
_Gcopystack |
Stack is being copied, no code execution, not in the run queue |
_Gpreempted |
Blocked due to preemption, not executing user code and not in the run queue, waiting to be woken up |
_Gscan |
GC is scanning the stack space, no code execution, can coexist with other states |
Among these states, the more common ones are _Grunnable
, _Grunning
, _Gsyscall
, _Gwaiting
, and _Gpreempted
. We will focus on these states here. Goroutine state transitions are a complex process, and there are many methods to trigger goroutine state transitions. We cannot cover all transition routes here but will select some for discussion.
Although goroutine states defined during runtime are numerous and complex, we can aggregate these different states into three categories: Waiting, Runnable, and Running. During runtime, goroutines switch among these three states:
- Waiting: Goroutine is waiting for certain conditions to be met, such as the end of a system call. This includes states like
_Gwaiting
,_Gsyscall
, and_Gpreempted
. - Runnable: Goroutine is ready to run and can be scheduled for execution. If there are many goroutines in the program, each goroutine may wait for more time. This corresponds to
_Grunnable
. At this point, the goroutine is in the local queue of P or the global queue. - Running: Goroutine is running on a thread, corresponding to
_Grunning
.
Grunnable
A goroutine enters the Grunnable state under the following circumstances:
Goroutine Creation
In Go, including the main entry main
in the user program, all goroutines are created through runtime.newproc
-> runtime.newproc1
. The former is a wrapper for the latter. The go
keyword ultimately translates to a call to runtime.newproc
. When runtime.newproc1
completes resource allocation and initialization, the new task’s state is set to Grunnable and then added to the current P’s local task queue.
|
|
Wakeup of Blocked Tasks
When a blocked task (Gwaiting) is awakened due to certain conditions being met (such as writing data to a channel, which wakes up a task waiting to receive), the state of the waiting task (g1) is transitioned back to Grunnable and added to the task queue by calling runtime.ready
. There is a more detailed explanation of goroutine blocking.
|
|
Others
Another path is transitioning from Grunning and Gsyscall states to Grunnable, which will be discussed later. In short, a task in the Grunnable state must be in a task queue and ready to be scheduled for execution.
Grunning
All tasks in the Grunnable state may be retrieved by the scheduler (P&M) through the findrunnable
function. Subsequently, their state is transitioned to Grunning, and finally, runtime.gogo
is called to load the context and execute.
|
|
|
|
Go adopts a cooperative scheduling scheme. A running task needs to explicitly yield the processor.
After Go 1.2, the runtime also supports a certain degree of task preemption. When the system thread sysmon
detects that a task is taking too long to execute or the runtime determines that garbage collection is necessary, the task is marked as “preemptible”. Upon the next function call of the task, it yields the processor and switches back to the Grunnable state.
Gsyscall
To ensure high concurrency performance, the Go runtime first sets its state to Gsyscall before executing OS system calls using the runtime.entersyscall
function (if the system call is blocking or takes too long to execute, the current M is detached from P). Upon return from the system call, the thread calls runtime.exitsyscall
to attempt to regain P. If successful and the current task has not been preempted, its state transitions back to Grunning for continued execution. Otherwise, it is set to Grunnable and waits to be scheduled for execution again.
|
|
|
|
Gwaiting
When a task’s required resource or running condition cannot be met, it needs to call the runtime.park
function to enter this state. Subsequently, unless the waiting condition is met, the task will remain in the waiting state and cannot execute. Apart from the example of channels mentioned earlier, Go’s timers, network IO operations, atomics, and semaphores can all cause tasks to block.
|
|
|
|
In runtime.park
, lock
is the lock that the goroutine needs to release when it is blocked (such as in channels), and reason
is the reason for blocking, which facilitates debugging with gdb. When all tasks are in the Gwaiting state, it means that the current program has entered a deadlock. In this case, the runtime detects this situation and outputs the backtrace information of all Gwaiting tasks.
Gdead
When a task completes execution, it calls runtime.goexit
to end. Its state is set to Gdead, and it enters the gFree list of the current P.
Conclusion
The state transitions of goroutines are similar to those of thread state transitions, but they appear more complex due to garbage collection reasons. However, if we remove the GC part, the state transitions of goroutines are similar to those of thread state transitions.
In the next article, I will summarize and analyze the state transitions of P