Each column below is a CPU core with a queue of tasks (colored blocks). Cores drain their queue from the top; when a core goes idle it steals from the bottom of the longest neighbor's queue.
<!-- Work-stealing demo: 4 cores, each with a queue of tasks (top=active, bottom=steal target) -->
<div class="toolbar">
<button id="btn-start" type="button">Start</button>
<button id="btn-steal-toggle" type="button" class="ghost">Stealing: ON</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="cores-row" class="cores-row"></div>
<div class="legend-row">
<span class="legend-item"><span class="legend-dot busy"></span>Busy</span>
<span class="legend-item"><span class="legend-dot idle"></span>Idle</span>
<span class="legend-item"><span class="legend-dot stealing"></span>Stealing</span>
</div>
<div id="stats" class="stats"></div>
/* Demo layout for work-stealing scheduler simulation */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.toolbar { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .75rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
/* Core columns, each holding a vertical stack of task blocks */
.cores-row { display: flex; gap: 10px; flex-wrap: wrap; }
.core-col { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 60px; }
.core-label { font: 600 11px system-ui; color: #555; text-transform: uppercase; letter-spacing: .04em; }
.core-box { width: 56px; min-height: 160px; border: 2px solid #c0c8d0; border-radius: 8px;
display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
padding: 4px; gap: 3px; transition: border-color .2s; }
.core-box.busy { border-color: #2196f3; }
.core-box.idle { border-color: #aaa; }
.core-box.stealing { border-color: #ff9800; background: #fff8f0; }
/* Individual task block — color = origin core, number = remaining ticks */
.task { width: 44px; height: 20px; border-radius: 4px; display: flex; align-items: center;
justify-content: center; font: 700 10px ui-monospace; color: #fff;
transition: opacity .15s; }
.task.active { outline: 2px solid #fff; outline-offset: -2px; }
.legend-row { display: flex; gap: 14px; margin-top: .5rem; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: 5px; font-size: .78rem; color: #555; }
.legend-dot { width: 12px; height: 12px; border-radius: 50%; border: 2px solid; }
.legend-dot.busy { border-color: #2196f3; }
.legend-dot.idle { border-color: #aaa; }
.legend-dot.stealing { border-color: #ff9800; background: #fff8f0; }
.stats { margin-top: .5rem; font-size: .82rem; color: #555; min-height: 1.3em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Work-stealing scheduler simulation: cores drain their own queue top-to-bottom; idle cores steal from the bottom of the longest neighbor's queue.
// Simulation configuration
const NUM_CORES = 4;
const TASK_COLORS = ['#e63946','#2a9d8f','#e9c46a','#f4a261','#264653','#6a4c93','#43aa8b','#f94144'];
let stealingEnabled = true;
let running = false;
let rafId = null;
// Simulation state: cores array, task counters, steal counter
let cores = [];
let totalTasks = 0;
let completedTasks = 0;
let steals = 0;
function makeTask(id, color, weight) {
// Create a task: id, display color, total weight (ticks to complete)
return { id, color, weight, remaining: weight };
}
function initCores() {
// Initialize cores with uneven task loads to show the imbalance work-stealing fixes
cores = [];
totalTasks = 0;
completedTasks = 0;
steals = 0;
const taskId = { v: 0 };
for (let i = 0; i < NUM_CORES; i++) {
const queue = [];
// Skewed initial load: core 0 gets 8 tasks, core 1 gets 6, cores 2-3 get 2 each
const taskCount = i === 0 ? 8 : i === 1 ? 6 : 2;
for (let t = 0; t < taskCount; t++) {
const color = TASK_COLORS[(taskId.v) % TASK_COLORS.length];
const weight = 3 + Math.floor(Math.random() * 5);
queue.push(makeTask(taskId.v++, color, weight));
totalTasks++;
}
cores.push({ queue, status: 'idle', active: null });
}
}
// Render all cores and their task queues to the DOM
function render() {
const row = document.getElementById('cores-row');
row.innerHTML = '';
for (let i = 0; i < cores.length; i++) {
const c = cores[i];
const col = document.createElement('div');
col.className = 'core-col';
const label = document.createElement('div');
label.className = 'core-label';
label.textContent = 'Core ' + (i + 1);
col.appendChild(label);
const box = document.createElement('div');
box.className = 'core-box ' + c.status;
// Show the currently executing task at the top of each core box
if (c.active) {
const el = document.createElement('div');
el.className = 'task active';
el.style.background = c.active.color;
el.textContent = c.active.remaining;
box.appendChild(el);
}
for (const t of c.queue) {
const el = document.createElement('div');
el.className = 'task';
el.style.background = t.color;
el.textContent = t.remaining;
box.appendChild(el);
}
col.appendChild(box);
row.appendChild(col);
}
updateStats();
}
function updateStats() {
const done = completedTasks;
const total = totalTasks;
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
let msg = i18nFmt('Tasks: %done% / %total% (%pct%%)', { done, total, pct });
if (steals > 0) msg += ' ' + i18nFmt('| steals: %steals%', { steals });
if (done === total && total > 0) msg = 'All tasks complete!';
document.getElementById('stats').textContent = msg;
}
// Animation tick: advance one scheduling step per interval
let lastTime = 0;
const TICK_MS = 350;
function tick(now) {
if (now - lastTime < TICK_MS) { rafId = requestAnimationFrame(tick); return; }
lastTime = now;
// Step 1: each core advances its active task by one tick, or picks the next from its queue
let allDone = true;
for (const c of cores) {
if (c.active) {
allDone = false;
c.active.remaining--;
if (c.active.remaining <= 0) {
completedTasks++;
c.active = null;
c.status = 'idle';
} else {
c.status = 'busy';
}
}
if (!c.active && c.queue.length > 0) {
allDone = false;
c.active = c.queue.shift();
c.status = 'busy';
}
}
// Step 2 (if stealing enabled): idle cores with empty queues steal from the longest neighbor
if (stealingEnabled) {
for (const c of cores) {
if (!c.active && c.queue.length === 0) {
// Find the neighbor core with the most queued tasks
let victim = null;
let maxLen = 1;
for (const other of cores) {
if (other !== c && other.queue.length > maxLen) {
maxLen = other.queue.length;
victim = other;
}
}
if (victim) {
// Steal from the bottom of the victim's queue (pop = bottom, mimicking deque steal)
const stolen = victim.queue.pop();
c.queue.unshift(stolen);
c.status = 'stealing';
steals++;
allDone = false;
} else {
c.status = c.active ? 'busy' : 'idle';
}
}
}
}
render();
if (allDone && completedTasks === totalTasks) {
running = false;
document.getElementById('btn-start').disabled = false;
} else if (running) {
rafId = requestAnimationFrame(tick);
}
}
function start() {
if (running) return;
running = true;
document.getElementById('btn-start').disabled = true;
lastTime = 0;
rafId = requestAnimationFrame(tick);
}
function reset() {
running = false;
if (rafId) cancelAnimationFrame(rafId);
initCores();
document.getElementById('btn-start').disabled = false;
render();
}
function toggleStealing() {
stealingEnabled = !stealingEnabled;
const btn = document.getElementById('btn-steal-toggle');
btn.textContent = stealingEnabled ? 'Stealing: ON' : 'Stealing: OFF';
}
document.getElementById('btn-start').addEventListener('click', start);
document.getElementById('btn-reset').addEventListener('click', reset);
document.getElementById('btn-steal-toggle').addEventListener('click', toggleStealing);
// Initialize and render on load
initCores();
render();
<!-- Work-stealing demo: 4 cores, each with a queue of tasks (top=active, bottom=steal target) -->
<div class="toolbar">
<button id="btn-start" type="button">Start</button>
<button id="btn-steal-toggle" type="button" class="ghost">Stealing: ON</button>
<button id="btn-reset" type="button" class="ghost">Reset</button>
</div>
<div id="cores-row" class="cores-row"></div>
<div class="legend-row">
<span class="legend-item"><span class="legend-dot busy"></span>Busy</span>
<span class="legend-item"><span class="legend-dot idle"></span>Idle</span>
<span class="legend-item"><span class="legend-dot stealing"></span>Stealing</span>
</div>
<div id="stats" class="stats"></div>
/* Demo layout for work-stealing scheduler simulation */
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; color: #222; margin: 0; padding: .5rem; }
.toolbar { display: flex; gap: .5rem; flex-wrap: wrap; margin-bottom: .75rem; }
button { font: 600 13px system-ui; padding: .4rem .85rem; border: 1px solid #1d3557;
background: #1d3557; color: #fff; border-radius: 7px; cursor: pointer; }
button.ghost { background: #fff; color: #1d3557; }
button:disabled { opacity: .4; cursor: default; }
/* Core columns, each holding a vertical stack of task blocks */
.cores-row { display: flex; gap: 10px; flex-wrap: wrap; }
.core-col { display: flex; flex-direction: column; align-items: center; gap: 4px; min-width: 60px; }
.core-label { font: 600 11px system-ui; color: #555; text-transform: uppercase; letter-spacing: .04em; }
.core-box { width: 56px; min-height: 160px; border: 2px solid #c0c8d0; border-radius: 8px;
display: flex; flex-direction: column; align-items: center; justify-content: flex-start;
padding: 4px; gap: 3px; transition: border-color .2s; }
.core-box.busy { border-color: #2196f3; }
.core-box.idle { border-color: #aaa; }
.core-box.stealing { border-color: #ff9800; background: #fff8f0; }
/* Individual task block — color = origin core, number = remaining ticks */
.task { width: 44px; height: 20px; border-radius: 4px; display: flex; align-items: center;
justify-content: center; font: 700 10px ui-monospace; color: #fff;
transition: opacity .15s; }
.task.active { outline: 2px solid #fff; outline-offset: -2px; }
.legend-row { display: flex; gap: 14px; margin-top: .5rem; flex-wrap: wrap; }
.legend-item { display: flex; align-items: center; gap: 5px; font-size: .78rem; color: #555; }
.legend-dot { width: 12px; height: 12px; border-radius: 50%; border: 2px solid; }
.legend-dot.busy { border-color: #2196f3; }
.legend-dot.idle { border-color: #aaa; }
.legend-dot.stealing { border-color: #ff9800; background: #fff8f0; }
.stats { margin-top: .5rem; font-size: .82rem; color: #555; min-height: 1.3em; }
const i18nFmt = (s, v) => s.replace(/%(\w+)%/g, (_, k) => String(v[k] ?? ''));
// Work-stealing scheduler simulation: cores drain their own queue top-to-bottom; idle cores steal from the bottom of the longest neighbor's queue.
// Simulation configuration
const NUM_CORES = 4;
const TASK_COLORS = ['#e63946','#2a9d8f','#e9c46a','#f4a261','#264653','#6a4c93','#43aa8b','#f94144'];
let stealingEnabled = true;
let running = false;
let rafId = null;
// Simulation state: cores array, task counters, steal counter
let cores = [];
let totalTasks = 0;
let completedTasks = 0;
let steals = 0;
function makeTask(id, color, weight) {
// Create a task: id, display color, total weight (ticks to complete)
return { id, color, weight, remaining: weight };
}
function initCores() {
// Initialize cores with uneven task loads to show the imbalance work-stealing fixes
cores = [];
totalTasks = 0;
completedTasks = 0;
steals = 0;
const taskId = { v: 0 };
for (let i = 0; i < NUM_CORES; i++) {
const queue = [];
// Skewed initial load: core 0 gets 8 tasks, core 1 gets 6, cores 2-3 get 2 each
const taskCount = i === 0 ? 8 : i === 1 ? 6 : 2;
for (let t = 0; t < taskCount; t++) {
const color = TASK_COLORS[(taskId.v) % TASK_COLORS.length];
const weight = 3 + Math.floor(Math.random() * 5);
queue.push(makeTask(taskId.v++, color, weight));
totalTasks++;
}
cores.push({ queue, status: 'idle', active: null });
}
}
// Render all cores and their task queues to the DOM
function render() {
const row = document.getElementById('cores-row');
row.innerHTML = '';
for (let i = 0; i < cores.length; i++) {
const c = cores[i];
const col = document.createElement('div');
col.className = 'core-col';
const label = document.createElement('div');
label.className = 'core-label';
label.textContent = 'Core ' + (i + 1);
col.appendChild(label);
const box = document.createElement('div');
box.className = 'core-box ' + c.status;
// Show the currently executing task at the top of each core box
if (c.active) {
const el = document.createElement('div');
el.className = 'task active';
el.style.background = c.active.color;
el.textContent = c.active.remaining;
box.appendChild(el);
}
for (const t of c.queue) {
const el = document.createElement('div');
el.className = 'task';
el.style.background = t.color;
el.textContent = t.remaining;
box.appendChild(el);
}
col.appendChild(box);
row.appendChild(col);
}
updateStats();
}
function updateStats() {
const done = completedTasks;
const total = totalTasks;
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
let msg = i18nFmt('Tasks: %done% / %total% (%pct%%)', { done, total, pct });
if (steals > 0) msg += ' ' + i18nFmt('| steals: %steals%', { steals });
if (done === total && total > 0) msg = 'All tasks complete!';
document.getElementById('stats').textContent = msg;
}
// Animation tick: advance one scheduling step per interval
let lastTime = 0;
const TICK_MS = 350;
function tick(now) {
if (now - lastTime < TICK_MS) { rafId = requestAnimationFrame(tick); return; }
lastTime = now;
// Step 1: each core advances its active task by one tick, or picks the next from its queue
let allDone = true;
for (const c of cores) {
if (c.active) {
allDone = false;
c.active.remaining--;
if (c.active.remaining <= 0) {
completedTasks++;
c.active = null;
c.status = 'idle';
} else {
c.status = 'busy';
}
}
if (!c.active && c.queue.length > 0) {
allDone = false;
c.active = c.queue.shift();
c.status = 'busy';
}
}
// Step 2 (if stealing enabled): idle cores with empty queues steal from the longest neighbor
if (stealingEnabled) {
for (const c of cores) {
if (!c.active && c.queue.length === 0) {
// Find the neighbor core with the most queued tasks
let victim = null;
let maxLen = 1;
for (const other of cores) {
if (other !== c && other.queue.length > maxLen) {
maxLen = other.queue.length;
victim = other;
}
}
if (victim) {
// Steal from the bottom of the victim's queue (pop = bottom, mimicking deque steal)
const stolen = victim.queue.pop();
c.queue.unshift(stolen);
c.status = 'stealing';
steals++;
allDone = false;
} else {
c.status = c.active ? 'busy' : 'idle';
}
}
}
}
render();
if (allDone && completedTasks === totalTasks) {
running = false;
document.getElementById('btn-start').disabled = false;
} else if (running) {
rafId = requestAnimationFrame(tick);
}
}
function start() {
if (running) return;
running = true;
document.getElementById('btn-start').disabled = true;
lastTime = 0;
rafId = requestAnimationFrame(tick);
}
function reset() {
running = false;
if (rafId) cancelAnimationFrame(rafId);
initCores();
document.getElementById('btn-start').disabled = false;
render();
}
function toggleStealing() {
stealingEnabled = !stealingEnabled;
const btn = document.getElementById('btn-steal-toggle');
btn.textContent = stealingEnabled ? 'Stealing: ON' : 'Stealing: OFF';
}
document.getElementById('btn-start').addEventListener('click', start);
document.getElementById('btn-reset').addEventListener('click', reset);
document.getElementById('btn-steal-toggle').addEventListener('click', toggleStealing);
// Initialize and render on load
initCores();
render();
Press Start to run the simulation. Toggle Stealing on/off to see what happens when idle cores just wait instead of stealing. With stealing off, fast cores finish early and sit idle while slow cores are still overwhelmed — the classic imbalance. With stealing on, the load levels out automatically.
Comments
Loading comments...