Skip to content

Commit 9535a18

Browse files
committed
feat(sched): implement actual load redistribution in schedule_balanced
schedule_balanced() previously computed load variance but never moved coroutines. Rewrite to actually migrate coroutines from overloaded processors to the global queue, where underloaded processors can steal. - Two-phase approach: collect from local queues under trylock, then bulk-insert into global queue to avoid lock-order violations - Wakes idle workers when new work is added to global queue - Triggers when a P's load exceeds 150% of average - Adds test_sched_balanced
1 parent f42e420 commit 9535a18

3 files changed

Lines changed: 123 additions & 14 deletions

File tree

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ if(COCO_BUILD_TESTS)
224224
test_platform_abstraction
225225
test_sched_stats
226226
test_sched_reinit
227+
test_sched_balanced
227228
test_coco_go
228229
test_context_value
229230
test_frame_walker

src/sched/sched.c

Lines changed: 95 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -172,40 +172,121 @@ void schedule_ready(coco_coro_t *g) {
172172
coco_global_runq_put(g);
173173
}
174174

175-
/* 负载均衡检查 */
175+
/* 负载均衡: 从过载处理器迁移协程到全局队列 */
176176
bool schedule_balanced(coco_global_sched_t *sched) {
177177
if (!sched || sched->processor_count == 0) {
178178
return true;
179179
}
180180

181181
/* 计算平均负载 */
182-
uint64_t total = 0;
182+
uint64_t total = coco_global_runq_size();
183183
for (uint32_t i = 0; i < sched->processor_count; i++) {
184184
coco_processor_t *p = coco_processor_get(i);
185185
if (p) {
186186
total += runq_size(p);
187187
}
188188
}
189-
total += coco_global_runq_size();
190-
191189
double avg = (double)total / sched->processor_count;
192-
if (avg == 0) {
193-
return true;
190+
if (avg < 4.0) {
191+
return true; /* 负载不足,无需均衡 */
194192
}
195193

196-
/* 检查方差 */
197-
double variance = 0;
194+
/* Phase 1: 在 trylock 下从过载 P 收集协程到临时链表 */
195+
coco_coro_t *to_move_head = NULL;
196+
coco_coro_t *to_move_tail = NULL;
197+
uint32_t moved_count = 0;
198+
198199
for (uint32_t i = 0; i < sched->processor_count; i++) {
199200
coco_processor_t *p = coco_processor_get(i);
200-
if (p) {
201-
double diff = runq_size(p) - avg;
202-
variance += diff * diff;
201+
if (!p) {
202+
continue;
203+
}
204+
205+
uint32_t size = runq_size(p);
206+
if (size <= avg * 1.5) {
207+
continue; /* 未过载 */
208+
}
209+
210+
uint32_t target = (uint32_t)(avg * 1.2);
211+
uint32_t n = (size > target) ? (size - target) : 0;
212+
if (n == 0) {
213+
continue;
214+
}
215+
216+
if (pthread_mutex_trylock(&p->local_runq_lock) != 0) {
217+
continue; /* 锁竞争,跳过此 P */
218+
}
219+
220+
for (uint32_t j = 0; j < n; j++) {
221+
/* 直接从队列头部取出(与 runq_get 逻辑一致,但已持有锁) */
222+
coco_coro_t *g = p->local_runq_head;
223+
if (!g) {
224+
break;
225+
}
226+
p->local_runq_head = g->next;
227+
if (p->local_runq_head) {
228+
p->local_runq_head->prev = NULL;
229+
} else {
230+
p->local_runq_tail = NULL;
231+
}
232+
p->local_runq_size--;
233+
234+
/* 从原链表分离并加入临时链表 */
235+
g->prev = NULL;
236+
g->next = NULL;
237+
if (to_move_tail) {
238+
to_move_tail->next = g;
239+
to_move_tail = g;
240+
} else {
241+
to_move_head = to_move_tail = g;
242+
}
243+
moved_count++;
244+
}
245+
pthread_mutex_unlock(&p->local_runq_lock);
246+
}
247+
248+
if (moved_count == 0) {
249+
return true;
250+
}
251+
252+
/* Phase 2: 批量插入全局队列(不持有任何 local 锁) */
253+
extern int coco_preempt_block_signal(void);
254+
extern int coco_preempt_unblock_signal(void);
255+
256+
coco_preempt_block_signal();
257+
pthread_mutex_lock(&sched->global_runq_lock);
258+
259+
for (coco_coro_t *g = to_move_head; g;) {
260+
coco_coro_t *next = g->next;
261+
g->next = NULL;
262+
g->prev = sched->global_runq_tail;
263+
if (sched->global_runq_tail) {
264+
sched->global_runq_tail->next = g;
265+
} else {
266+
sched->global_runq_head = g;
267+
}
268+
sched->global_runq_tail = g;
269+
g = next;
270+
}
271+
sched->global_runq_size += moved_count;
272+
273+
pthread_mutex_unlock(&sched->global_runq_lock);
274+
coco_preempt_unblock_signal();
275+
276+
/* 唤醒空闲 worker 从全局队列窃取 */
277+
uint32_t idle = atomic_load(&sched->idle_count);
278+
if (idle > 0) {
279+
uint32_t to_wake = (moved_count < idle) ? moved_count : idle;
280+
coco_preempt_block_signal();
281+
pthread_mutex_lock(&sched->idle_lock);
282+
for (uint32_t i = 0; i < to_wake; i++) {
283+
pthread_cond_signal(&sched->idle_cond);
203284
}
285+
pthread_mutex_unlock(&sched->idle_lock);
286+
coco_preempt_unblock_signal();
204287
}
205-
variance /= sched->processor_count;
206288

207-
/* 方差 < 平均值的 20% */
208-
return variance < (avg * 0.2 * avg * 0.2);
289+
return true;
209290
}
210291

211292
/* 统计偷取成功率 */

tests/unit/test_sched_balanced.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "coco.h"
2+
#include <stdio.h>
3+
#include <assert.h>
4+
#include <stdatomic.h>
5+
6+
static _Atomic int counter = 0;
7+
8+
static void worker(void *arg) {
9+
(void)arg;
10+
for (int i = 0; i < 100; i++) {
11+
atomic_fetch_add(&counter, 1);
12+
}
13+
}
14+
15+
int main(void) {
16+
counter = 0;
17+
coco_global_sched_start(4);
18+
/* 注入大量协程,负载均衡应将其分布到各处理器 */
19+
for (int i = 0; i < 200; i++) {
20+
coco_go(worker, NULL);
21+
}
22+
coco_global_sched_wait();
23+
coco_global_sched_stop();
24+
assert(counter == 200 * 100);
25+
printf("test_sched_balanced: PASSED (counter=%d)\n", counter);
26+
return 0;
27+
}

0 commit comments

Comments
 (0)