jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

How Browsers Work · Part 20 — Threading Deep: Workers, SharedArrayBuffer & Atomics

Đa luồng thật trên web: Web Workers, transferable vs SharedArrayBuffer, Atomics để đồng bộ, và cross-origin isolation (COOP/COEP) mở khoá bộ nhớ chia sẻ cùng timer phân giải cao.

JavaScript chạy đơn luồng trên mỗi context (Phần 7), nhưng nền tảng web đa luồng thật qua Web Workers. Phần này đi sâu vào cách worker giao tiếp, cách chia sẻ bộ nhớ thật sự với SharedArrayBuffer, dùng Atomics để đồng bộ, và vì sao những thứ đó bị khoá sau cross-origin isolation.


1. Worker = execution context riêng, có thể chạy đồng thời

Mỗi worker có global scope/event loop riêng và không truy cập DOM của document. Engine có thể chạy worker đồng thời với main thread trên worker thread; chi tiết ánh xạ tới OS thread thuộc implementation. JS object thông thường không chia sẻ trực tiếp giữa các context — giao tiếp qua message hoặc shared memory có kiểm soát.

Main thread (window)          Worker thread
  event loop riêng              event loop riêng
  heap riêng       ◄─postMessage─►  heap riêng
  có DOM                         KHÔNG có DOM

Ba loại worker:

LoạiPhạm viDùng cho
Dedicated Workermột trang sở hữutính toán nặng riêng cho trang
Shared Workernhiều context cùng origin/storage partitionchia sẻ trạng thái/kết nối khi được hỗ trợ
Service Workerlifecycle/proxy mạng riêng (Phần 10)offline, cache, push; không phải compute worker sống mãi
const w = new Worker(new URL('./worker.js', import.meta.url), { type: 'module' });
w.postMessage({ cmd: 'crunch', data });
w.onmessage = (e) => render(e.data);

2. postMessage: structured clone vs transfer

Mặc định, postMessage serialize/deserialize graph dữ liệu bằng structured clone. Với object/buffer lớn, việc tạo bản sao có thể tốn CPU và memory.

structured clone:  [data] ──copy──► [data']   (cả hai bên đều có bản)
transfer:          [buffer] ──move──► [buffer] (bên gửi bị detach)

Transferable (ArrayBuffer, MessagePort, ImageBitmap, OffscreenCanvas…) có semantics chuyển ownership: object phía gửi bị detach/không dùng lại được, còn phía nhận có object mới trỏ tới backing resource đã chuyển. Cơ chế này tránh structured-clone toàn bộ payload đắt tiền; đừng phụ thuộc vào chi tiết “physical zero-copy” của implementation.

const buf = new ArrayBuffer(64 * 1024 * 1024);
worker.postMessage({ buf }, [buf]); // transfer list → ownership chuyển đi
// buf.byteLength === 0 ở đây: đã mất quyền

Dùng transfer cho buffer lớn để tránh chi phí clone. Nhưng transfer vẫn là chuyển, không phải chia sẻ — chỉ một bên giữ tại một thời điểm.


3. SharedArrayBuffer — bộ nhớ chia sẻ thật

SharedArrayBuffer (SAB) là vùng nhớ nhiều luồng cùng đọc/ghi đồng thời — không copy, không transfer. Đây là chia sẻ bộ nhớ thật như trong ngôn ngữ đa luồng.

const sab = new SharedArrayBuffer(1024);
const view = new Int32Array(sab);
worker.postMessage({ sab });   // KHÔNG transfer — cả hai cùng thấy cùng vùng nhớ
// Dùng Atomics để publication/ordering rõ ràng giữa các agent
Atomics.store(view, 0, 42);

Các byte thực sự được chia sẻ, nhưng câu “luồng kia thấy ngay” không phải memory model. Nhiều agent đọc/ghi không phối hợp tạo race hoặc ordering khó đoán; dùng Atomics cho synchronization/publication.


4. Atomics — đọc/ghi nguyên tử & đồng bộ

Atomics cung cấp thao tác nguyên tử trên integer typed array được backed bởi SAB, cùng cơ chế chờ/đánh thức để xây primitive đồng bộ.

const view = new Int32Array(sab);

// Thao tác nguyên tử (an toàn khi nhiều luồng cùng đụng)
Atomics.add(view, 0, 1);          // tăng nguyên tử
const v = Atomics.load(view, 0);  // đọc nguyên tử

// worker.js: luôn kiểm tra điều kiện trong vòng lặp
while (Atomics.load(view, 0) === 0) {
  Atomics.wait(view, 0, 0); // ngủ nếu giá trị vẫn bằng expected (0)
}

Ở agent khác (ví dụ main thread), cập nhật state trước rồi mới notify:

Atomics.store(view, 0, 1);
Atomics.notify(view, 0, 1);

Atomics.wait() chỉ hợp lệ trên agent được phép block (web main thread sẽ throw). Atomics.notify() chỉ đánh thức waiter; nó không tự thay đổi dữ liệu, nên waiter phải kiểm tra lại điều kiện. Khi được hỗ trợ, Atomics.waitAsync() dùng được ở main thread mà không block; API trả { async, value }, trong đó value là Promise hoặc kết quả tức thì:

const result = Atomics.waitAsync(view, 0, 0);
await result.value;

5. Cross-origin isolation — vì sao SAB bị khoá

Sau Spectre, browser hạn chế SharedArrayBuffer và timer chính xác vì chúng làm timing side-channel mạnh hơn. Trên web, cần secure context và trạng thái cross-origin isolated. Cặp header phổ biến, interoperable nhất là:

Cross-Origin-Opener-Policy:   same-origin       (COOP — tách browsing context group)
Cross-Origin-Embedder-Policy: require-corp       (COEP — chặn tài nguyên cross-origin chưa opt-in)
if (crossOriginIsolated) {
  // mới được dùng SharedArrayBuffer, performance.measureUserAgentSpecificMemory,
  // và timer performance.now() phân giải cao hơn
}

Mô hình: COOP tách top-level document sang browsing context group phù hợp; COEP chỉ cho nhúng cross-origin resource đã opt in qua CORP hoặc CORS. Một số browser còn hỗ trợ COEP: credentialless, nhưng support/trade-off khác nhau. Đây là web opt-in policy, bổ sung chứ không đồng nhất với Site Isolation nội bộ ở Phần 21. Hãy kiểm tra kết quả bằng self.crossOriginIsolated và audit popup/OAuth/third-party embeds trước khi deploy.


6. Khi nào dùng gì

Tính toán nặng, dữ liệu copy được      → Worker + structured clone
Buffer lớn, gửi một chiều              → Worker + transfer ownership
Nhiều luồng cùng truy cập state nóng   → SharedArrayBuffer + Atomics (cần COOP/COEP)
Vẽ canvas khi main bận                  → OffscreenCanvas trong worker (Phần 19)
Chia sẻ message giữa nhiều tab         → SharedWorker / BroadcastChannel (cùng partition)

Đừng vội dùng SAB: với hầu hết ứng dụng, worker + transfer là đủ và đơn giản hơn nhiều. SAB/Atomics dành cho wasm threads, xử lý audio/video realtime, hoặc cấu trúc dữ liệu chia sẻ hiệu năng cao.


7. Tóm tắt

  • Worker cho execution context/event loop riêng có thể chạy đồng thời; dedicated/shared/service worker có lifecycle và mục đích khác nhau.
  • postMessage dùng structured clone; transferable chuyển ownership và detach phía gửi, tránh clone payload đắt tiền.
  • SharedArrayBuffer chia sẻ bộ nhớ thật giữa các luồng (không copy/transfer) → cần đồng bộ.
  • Atomics cung cấp operation/ordering nguyên tử; wait cần vòng lặp điều kiện và không được block main thread, còn waitAsync phải feature-detect.
  • SAB trên web cần secure context + cross-origin isolation; cặp phổ biến là COOP: same-origin + COEP: require-corp, kiểm tra self.crossOriginIsolated.
  • Chọn công cụ theo nhu cầu: copy → clone, buffer lớn một chiều → transfer, state nóng đa luồng → SAB+Atomics.

Phần tiếp theo: Process model deep — Site Isolation và OOPIF, Back/Forward Cache (BFCache), prerender/speculation rules, và Page Lifecycle API điều phối tài nguyên giữa các tab.