v / vlib / goroutines / goroutines.v
228 lines · 193 sloc · 6.45 KB · fe591414652213de02e6c0ba87c03fc7a4d6ebb4
Raw
1// Copyright (c) 2026 Alexander Medvednikov. All rights reserved.
2// Use of this source code is governed by an MIT license
3// that can be found in the LICENSE file.
4//
5// Go-style goroutine runtime for V.
6// Implements the GMP (Goroutine-Machine-Processor) scheduling model
7// translated from the Go runtime (src/runtime/proc.go, runtime2.go).
8//
9// Goroutine - goroutine: lightweight unit of execution with its own stack
10// Machine - machine: OS thread that executes goroutines
11// Processor - processor: logical processor that manages a local run queue
12//
13// Design doc: https://golang.org/s/go11sched
14@[has_globals]
15module goroutines
16
17import sync
18import runtime as _
19
20// GoFn is the type for a goroutine function: takes a voidptr argument.
21pub type GoFn = fn (voidptr)
22
23// Default goroutine stack size.
24// Go uses 8KB with growable stacks; V/C needs more since stacks are fixed.
25const default_stack_size = 256 * 1024
26
27// Maximum number of goroutines in a Processor's local run queue (matches Go's 256)
28const local_queue_size = 256
29
30// How often to check the global queue for fairness (matches Go's 61)
31const global_queue_check_interval = 61
32
33// GStatus represents goroutine states (matches Go's runtime2.go)
34pub enum GStatus {
35 idle // just allocated, not yet initialized
36 runnable // on a run queue, not currently executing
37 running // executing user code on an Machine
38 waiting // blocked (channel, mutex, etc.)
39 dead // finished execution, available for reuse
40 copystack // stack is being moved (not used yet)
41}
42
43// PStatus represents processor states
44pub enum PStatus {
45 idle // not being used, available on idle list
46 running // owned by an Machine and executing code
47 stopped // halted
48 dead // no longer used
49}
50
51// Goroutine represents a goroutine - the fundamental unit of concurrent execution.
52// Translated from Go's `type g struct` in runtime2.go.
53pub struct Goroutine {
54pub mut:
55 id u64 // unique goroutine id
56 status GStatus = .idle // current state
57 stack voidptr // stack memory (allocated)
58 stack_size int = default_stack_size // stack allocation size
59 context Context // saved CPU context for switching (ucontext_t or similar)
60 fn_ptr voidptr // function to execute
61 fn_arg voidptr // argument to the function
62 sched_link &Goroutine = unsafe { nil } // linked list link for run queues
63 m &Machine = unsafe { nil } // current Machine executing this Goroutine (nil if not running)
64 parent_id u64 // goroutine id of creator
65 wait_reason string // if status==waiting, why
66 preempt bool // preemption signal
67}
68
69// Machine represents a machine (OS thread) that executes goroutines.
70// Translated from Go's `type m struct` in runtime2.go.
71pub struct Machine {
72pub mut:
73 id i64
74 g0 &Goroutine = unsafe { nil } // goroutine with scheduling stack
75 curg &Goroutine = unsafe { nil } // current running goroutine
76 p &Processor = unsafe { nil } // attached processor (nil if not executing Go code)
77 spinning bool // looking for work
78 blocked bool // blocked on a note
79 park sync.Semaphore // for parking/unparking
80 sched_link &Machine = unsafe { nil } // linked list for idle Machine list
81 thread thread // underlying OS thread handle
82}
83
84// Processor represents a processor - a resource required to execute goroutines.
85// Each Processor has a local run queue. Translated from Go's `type p struct` in runtime2.go.
86pub struct Processor {
87pub mut:
88 id i32
89 status PStatus = .idle
90 m &Machine = unsafe { nil } // back-link to associated Machine
91 sched_tick u32 // incremented on every scheduler call
92
93 // Local run queue - lock-free SPMC ring buffer (matches Go's design)
94 runq_head u32 // consumer index (atomic)
95 runq_tail u32 // producer index (atomic)
96 runq [local_queue_size]&Goroutine // circular buffer
97 runnext &Goroutine = unsafe { nil } // next Goroutine to run (fast path, like Go's runnext)
98
99 // Free Goroutine list for reuse
100 g_free GoroutineList
101
102 // ID cache to avoid contention on the global counter
103 goid_cache u64
104 goid_cache_end u64
105
106 link &Processor = unsafe { nil } // linked list for idle Processor list
107}
108
109// Sched is the global scheduler state.
110// Translated from Go's `type schedt struct` in runtime2.go.
111pub struct Sched {
112pub mut:
113 goid_gen u64 // global goroutine ID generator (atomic)
114
115 mu SpinLock // protects idle lists, global queue (spinlock is ucontext-safe)
116
117 // Idle Machine's waiting for work
118 midle &Machine = unsafe { nil }
119 nmidle i32
120
121 // Idle Processor's
122 pidle &Processor = unsafe { nil }
123 npidle i32
124
125 // Global run queue
126 runq GoroutineQueue
127 nmspinning i32 // number of spinning Machine's (atomic)
128
129 // All Processor's (indexed by id)
130 allp []&Processor
131
132 // Total Machine count
133 mnext i64
134 maxmcount i32 = 10000
135
136 // Global Goroutine free list
137 g_free_mu SpinLock
138 g_free GoroutineList
139 g_free_count i32
140
141 // Shutdown
142 stopped bool
143}
144
145// GoroutineQueue is a simple linked-list queue of Goroutine's (matches Go's gQueue).
146pub struct GoroutineQueue {
147pub mut:
148 head &Goroutine = unsafe { nil }
149 tail &Goroutine = unsafe { nil }
150 size i32
151}
152
153// GoroutineList is a list of Goroutine's.
154pub struct GoroutineList {
155pub mut:
156 head &Goroutine = unsafe { nil }
157 count i32
158}
159
160fn (mut q GoroutineQueue) push_back(gp &Goroutine) {
161 mut g := unsafe { gp }
162 g.sched_link = unsafe { nil }
163 if q.tail != unsafe { nil } {
164 q.tail.sched_link = g
165 } else {
166 q.head = g
167 }
168 q.tail = g
169 q.size++
170}
171
172fn (mut q GoroutineQueue) push(gp &Goroutine) {
173 mut g := unsafe { gp }
174 g.sched_link = q.head
175 q.head = g
176 if q.tail == unsafe { nil } {
177 q.tail = g
178 }
179 q.size++
180}
181
182fn (mut q GoroutineQueue) pop() &Goroutine {
183 if q.head == unsafe { nil } {
184 return unsafe { nil }
185 }
186 gp := q.head
187 q.head = gp.sched_link
188 if q.head == unsafe { nil } {
189 q.tail = unsafe { nil }
190 }
191 q.size--
192 return gp
193}
194
195fn (q &GoroutineQueue) empty() bool {
196 return q.head == unsafe { nil }
197}
198
199fn (mut l GoroutineList) push(gp &Goroutine) {
200 mut g := unsafe { gp }
201 g.sched_link = l.head
202 l.head = g
203 l.count++
204}
205
206fn (mut l GoroutineList) pop() &Goroutine {
207 if l.head == unsafe { nil } {
208 return unsafe { nil }
209 }
210 gp := l.head
211 l.head = gp.sched_link
212 l.count--
213 return gp
214}
215
216fn (l &GoroutineList) empty() bool {
217 return l.head == unsafe { nil }
218}
219
220// Global scheduler instance
221__global gsched = Sched{}
222
223// Number of processors (defaults to number of CPU cores)
224__global gomaxprocs = i32(0)
225
226// All goroutines ever created (for debugging)
227__global allgs_mu = SpinLock{}
228__global allgs = []&Goroutine{}
229