`SharedArrayBuffer` trong TypeScript
Bài viết này giải thích về SharedArrayBuffer trong TypeScript.
Chúng tôi sẽ giải thích về SharedArrayBuffer trong TypeScript kèm theo các ví dụ thực tiễn.
YouTube Video
SharedArrayBuffer trong TypeScript
SharedArrayBuffer là một cơ chế cho phép chia sẻ cùng một vùng nhớ giữa nhiều luồng, ví dụ như Web Workers. Kết hợp với Atomics, bạn có thể kiểm soát các tranh chấp dữ liệu và thực hiện các thao tác chia sẻ bộ nhớ độ trễ thấp.
Điều kiện tiên quyết và Lưu ý
Khi sử dụng SharedArrayBuffer trên trình duyệt, cần thiết lập các header COOP và COEP để đáp ứng yêu cầu bảo mật gọi là cross-origin isolation. Trong Node.js, có thể xử lý bộ nhớ chia sẻ dễ dàng hơn bằng cách sử dụng worker_threads.
Khái niệm cơ bản
SharedArrayBuffer là một đối tượng đại diện cho một dãy byte có độ dài cố định, cho phép đọc và ghi số thông qua TypedArray hoặc các view tương tự. Các thao tác đọc/ghi đơn giản không được đồng bộ, vì vậy bạn cần sử dụng API Atomics để đảm bảo thao tác nguyên tử và tận dụng cơ chế wait và notify để phối hợp.
Bộ đếm đơn giản (Phiên bản trên trình duyệt)
Trong ví dụ này, luồng chính tạo SharedArrayBuffer và chuyển nó cho một Web Worker, cả hai cùng tăng một bộ đếm dùng chung. Ví dụ này minh họa mẫu tối thiểu: cộng nguyên tử bằng Atomics.add và đọc bằng Atomics.load.
main.ts (Phía trình duyệt)
Ví dụ này trình bày cách luồng chính tạo ra một SharedArrayBuffer và chia sẻ với Worker để truy cập đa luồng.
1// main.ts
2// Create a 4-byte (Int32) shared buffer for one counter
3const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT * 1);
4const counter = new Int32Array(sab); // view over the buffer
5
6// Create worker and transfer the SharedArrayBuffer
7const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
8worker.postMessage({ sab });
9
10// Increase counter in main thread every 200ms
11setInterval(() => {
12 const newVal = Atomics.add(counter, 0, 1) + 1; // Atomically increment
13 console.log('Main incremented ->', newVal);
14}, 200);
15
16// Listen for messages (optional)
17worker.onmessage = (e) => {
18 console.log('From worker:', e.data);
19};- Trong đoạn mã này, luồng chính sử dụng
Atomics.addđể tăng giá trị một cách nguyên tử. Ở phíaworker.ts, có thể truy cập và thao tác với cùng mộtSharedArrayBuffer.
worker.ts (Worker trên trình duyệt)
Đây là ví dụ trong đó worker nhận cùng buffer chia sẻ và thực hiện giảm hoặc thao tác định kỳ lên nó.
1// worker.ts
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const counter = new Int32Array(sab);
5
6 // Every 350ms, atomically add 2 (demonstration)
7 setInterval(() => {
8 const newVal = Atomics.add(counter, 0, 2) + 2;
9 // Optionally notify main thread (postMessage)
10 self.postMessage(`Worker added 2 -> ${newVal}`);
11 }, 350);
12};- Worker cũng thao tác trên cùng bộ nhớ thông qua
Int32Array, và các cập nhật diễn ra không có tranh chấp dữ liệu nhờ vàoAtomics.
Đồng bộ hóa bằng cách sử dụng wait/notify
Bằng cách sử dụng Atomics.wait và Atomics.notify, bạn có thể tạm ngừng các luồng cho đến khi đáp ứng điều kiện nhất định, cho phép đồng bộ hóa dựa trên sự kiện giữa các worker. Trên trình duyệt, cách an toàn nhất là sử dụng Atomics.wait bên trong Worker.
producer.ts (Worker Sản xuất trên trình duyệt)
Worker sản xuất ghi dữ liệu và thông báo cho worker tiêu thụ bằng notify.
1// producer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab); // state[0] will be 0=empty,1=filled
5
6 // produce every 500ms
7 setInterval(() => {
8 // produce: set state to 1 (filled)
9 Atomics.store(state, 0, 1);
10 Atomics.notify(state, 0, 1); // wake one waiter
11 self.postMessage('produced');
12 }, 500);
13};consumer.ts (Worker Tiêu thụ trên trình duyệt)
Worker tiêu thụ chờ bằng Atomics.wait và tiếp tục xử lý khi nhận thông báo từ worker sản xuất.
1// consumer.ts (worker)
2self.onmessage = (ev: MessageEvent) => {
3 const { sab } = ev.data as { sab: SharedArrayBuffer };
4 const state = new Int32Array(sab);
5
6 // consumer loop
7 (async function consumeLoop() {
8 while (true) {
9 // if state is 0 => wait until notified (value changes)
10 while (Atomics.load(state, 0) === 0) {
11 // Blocks this worker until notified or timeout
12 Atomics.wait(state, 0, 0);
13 }
14 // consume (reset to 0)
15 // (processing simulated)
16 // read data from another shared buffer in real scenarios
17 Atomics.store(state, 0, 0);
18 // continue loop
19 self.postMessage('consumed');
20 }
21 })();
22};- Trong mô hình này, worker sản xuất thông báo cho worker tiêu thụ bằng
Atomics.notifyvà worker tiêu thụ chờ một cách hiệu quả bằngAtomics.wait.Atomics.waitkhông thể gọi trên luồng chính của trình duyệt. Để tránh giao diện người dùng bị treo,Atomics.waitchỉ được phép sử dụng trong Worker.
Ví dụ thực tế với Node.js (worker_threads)
Trong môi trường Node.js, bạn có thể xử lý chức năng của SharedArrayBuffer bằng cách sử dụng worker_threads. Dưới đây là một ví dụ TypeScript có định kiểu dành cho Node.js.
main-node.ts
Luồng chính tạo buffer và chuyển cho Worker.
1// main-node.ts
2import { Worker } from 'worker_threads';
3import path from 'path';
4
5// Create SharedArrayBuffer for one 32-bit integer
6const sab = new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT);
7const counter = new Int32Array(sab);
8
9// Spawn worker and pass sab via workerData
10const worker = new Worker(path.resolve(__dirname, 'worker-node.js'), {
11 workerData: { sab }
12});
13
14// Log messages from worker
15worker.on('message', (msg) => console.log('Worker:', msg));
16
17// Increment in main thread periodically
18setInterval(() => {
19 const val = Atomics.add(counter, 0, 1) + 1;
20 console.log('Main increment ->', val);
21}, 300);worker-node.ts
Ví dụ này sử dụng parentPort và workerData từ worker_threads ở phía Worker.
1// worker-node.ts
2import { parentPort, workerData } from 'worker_threads';
3
4const sab = workerData.sab as SharedArrayBuffer;
5const counter = new Int32Array(sab);
6
7// Periodically add 5
8setInterval(() => {
9 const val = Atomics.add(counter, 0, 5) + 5;
10 parentPort?.postMessage(`Added 5 -> ${val}`);
11}, 700);- Trong Node.js, không bị hạn chế COOP hoặc COEP như trên trình duyệt, do vậy bộ nhớ chia sẻ có thể dễ dàng xử lý chỉ với
worker_threads. Khi sử dụng TypeScript, hãy chú ý bạn đang xây dựng với thiết lập CommonJS hay ESM.
Lưu ý khi định kiểu trong TypeScript
SharedArrayBuffer và Atomics đã được bao gồm trong các định nghĩa kiểu của DOM và thư viện chuẩn, nên bạn có thể dùng trực tiếp trong TypeScript. Khi trao đổi thông điệp với Worker, việc định nghĩa interface và chỉ rõ kiểu dữ liệu sẽ an toàn hơn.
1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };- Khai báo kiểu rõ ràng giúp việc xử lý
postMessagevàonmessagean toàn hơn và cho phép kiểm tra kiểu.
Các trường hợp sử dụng thực tiễn
SharedArrayBuffer không phải lúc nào cũng cần thiết, nhưng nó rất hiệu quả trong những trường hợp mà cần có lợi thế về tốc độ của bộ nhớ dùng chung. Hiểu được những tình huống mà nó hiệu quả sẽ giúp đưa ra lựa chọn công nghệ phù hợp.
- Nó phù hợp cho xử lý độ trễ thấp cần bộ nhớ chia sẻ tốc độ cao, ví dụ như xử lý âm thanh/hình ảnh thời gian thực hoặc vật lý trò chơi.
- Đối với trao đổi dữ liệu đơn giản hoặc truyền lượng lớn dữ liệu,
Transferable ArrayBufferhoặcpostMessagecó thể dễ dùng hơnSharedArrayBuffer.
Hạn chế và Bảo mật
Để sử dụng SharedArrayBuffer trên trình duyệt, cần cô lập nguồn chéo: đặt COOP là same-origin-allow-popups và COEP là require-corp. SharedArrayBuffer sẽ bị vô hiệu hóa nếu không đáp ứng các yêu cầu này.
Mẹo về hiệu năng và gỡ lỗi
Các thao tác nguyên tử (Atomics) nhanh, nhưng việc chờ đợi thường xuyên và đồng bộ hóa quá mức có thể làm tăng độ trễ.
Cần kiểm tra các điểm sau để xử lý bộ nhớ chia sẻ an toàn và hiệu quả.
- Các view như
Int32Arraycần được thao tác với căn chỉnh byte phù hợp. - Cần xác định rõ chỉ số nào của buffer chia sẻ được quá trình nào sử dụng, và duy trì quy ước nhất quán trong mã nguồn.
- Nếu bạn xử lý
SharedArrayBuffernhư mộtArrayBufferthông thường, sẽ xảy ra tranh chấp dữ liệu. Việc ghi vào cùng một chỉ số từ nhiều luồng mà không sử dụngAtomicslà rất nguy hiểm.
Tóm tắt
Khai thác đúng cách SharedArrayBuffer và Atomics sẽ giúp bạn thực hiện các thao tác chia sẻ bộ nhớ an toàn và nhanh chóng ngay cả trong TypeScript. Bắt đầu bằng các mô hình đồng bộ hóa đơn giản như bộ đếm hoặc tín hiệu sẽ dễ hiểu hơn, và khi kiểm soát tốt sự đồng bộ và chỉ số, bạn sẽ đạt hiệu quả ngay cả trong các trường hợp yêu cầu độ trễ thấp.
Bạn có thể làm theo bài viết trên bằng cách sử dụng Visual Studio Code trên kênh YouTube của chúng tôi. Vui lòng ghé thăm kênh YouTube.