`SharedArrayBuffer` dalam TypeScript

`SharedArrayBuffer` dalam TypeScript

Artikel ini menerangkan tentang SharedArrayBuffer dalam TypeScript.

Kami akan menerangkan SharedArrayBuffer dalam TypeScript dengan contoh-contoh praktikal.

YouTube Video

SharedArrayBuffer dalam TypeScript

SharedArrayBuffer ialah satu mekanisme untuk berkongsi ruang ingatan yang sama antara beberapa benang, seperti Web Workers. Dengan menggabungkannya bersama Atomics, anda boleh mengurus perlumbaan data (data races) dan melakukan operasi memori bersama dengan kependaman yang rendah.

Prasyarat dan Nota

Apabila menggunakan SharedArrayBuffer dalam pelayar, pengepala COOP dan COEP diperlukan untuk memenuhi keperluan keselamatan yang dikenali sebagai 'cross-origin isolation'. Dalam Node.js, memori bersama boleh dikendalikan dengan agak mudah menggunakan worker_threads.

Konsep Asas

SharedArrayBuffer ialah objek yang mewakili urutan bait sepanjang tetap, dan anda boleh membaca serta menulis nombor melalui TypedArray atau paparan serupa. Operasi baca/tulis yang mudah tidak diselaraskan, jadi anda perlu gunakan API Atomics untuk memastikan operasi atomik dan menggunakan mekanisme wait dan notify untuk penyelarasan.

Kaunter Mudah (Versi Pelayar)

Dalam contoh ini, benang utama mencipta SharedArrayBuffer dan menyerahkannya kepada Web Worker, dengan kedua-duanya menaikkan kaunter bersama. Ini menunjukkan corak paling asas: penambahan secara atomik dengan Atomics.add dan pembacaan dengan Atomics.load.

main.ts (Sisi Pelayar)

Contoh ini menunjukkan bagaimana benang utama mencipta SharedArrayBuffer dan berkongsikannya dengan Worker untuk capaian berbilang benang.

 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};
  • Dalam kod ini, benang utama menggunakan Atomics.add untuk menaikkan nilai secara atomik. Pada bahagian worker.ts, SharedArrayBuffer yang sama boleh dicapai dan dimanipulasi.

worker.ts (Worker Pelayar)

Ini adalah contoh di mana worker menerima buffer bersama yang sama dan secara berkala mengurangkan atau memanipulasikannya.

 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 juga memanipulasi memori yang sama melalui Int32Array, dan kemas kini dilakukan tanpa keadaan berlumba (race conditions) disebabkan penggunaan Atomics.

Penyelarasan menggunakan wait/notify

Dengan menggunakan Atomics.wait dan Atomics.notify, anda boleh menggantung benang sehingga syarat tertentu dipenuhi, membolehkan penyelarasan berasaskan peristiwa dalam benang worker. Dalam pelayar, kaedah paling selamat adalah menggunakan Atomics.wait di dalam Worker.

producer.ts (Worker Pengeluar Pelayar)

Pengeluar menulis data dan memberitahu pengguna (consumer) menggunakan 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 Pengguna Pelayar)

Pengguna (consumer) menunggu dengan Atomics.wait dan menyambung semula pemprosesan apabila menerima pemberitahuan daripada pengeluar.

 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};
  • Dalam corak ini, pengeluar memberitahu pengguna melalui Atomics.notify dan pengguna menunggu secara efisien menggunakan Atomics.wait. Atomics.wait tidak boleh dipanggil pada benang utama dalam pelayar. Bagi mengelakkan antara muka pengguna (UI) daripada membeku, Atomics.wait dihadkan untuk digunakan hanya dalam Worker sahaja.

Contoh Praktikal dengan Node.js (worker_threads)

Dalam persekitaran Node.js, anda boleh mengendalikan fungsi SharedArrayBuffer menggunakan worker_threads. Di bawah ini adalah contoh TypeScript dengan ‘typing’ untuk Node.js.

main-node.ts

Benang utama mencipta buffer dan menyerahkannya kepada 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

Contoh ini menggunakan parentPort dan workerData daripada worker_threads di bahagian 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);
  • Dalam Node.js, tiada sekatan COOP atau COEP seperti dalam pelayar, jadi memori bersama boleh dikendalikan dengan mudah hanya menggunakan worker_threads. Apabila menggunakan TypeScript, beri perhatian sama ada anda membina dengan tetapan CommonJS atau ESM.

Titik-titik Pengetikan TypeScript

SharedArrayBuffer dan Atomics sudah termasuk dalam definisi jenis (type definition) piawai DOM dan pustaka, jadi anda boleh menggunakannya secara langsung dalam TypeScript. Apabila bertukar mesej dengan Worker, adalah lebih selamat untuk menentukan antara muka (interface) dan jenis (type) dengan jelas.

1// Example: typed message
2type WorkerMessage = { type: 'init'; sab: SharedArrayBuffer } | { type: 'ping' };
  • Menetapkan jenis (type) secara jelas menjadikan pengendalian postMessage dan onmessage lebih selamat serta membolehkan semakan jenis (type checking).

Kes Penggunaan Praktikal

SharedArrayBuffer tidak sentiasa diperlukan, tetapi ia sangat berkesan dalam situasi di mana kelebihan kelajuan memori bersama diperlukan. Memahami situasi di mana ia berkesan akan membawa kepada pilihan teknologi yang sesuai.

  • Ia sesuai untuk pemprosesan kependaman rendah yang memerlukan buffer bersama berkelajuan tinggi dan boleh digunakan untuk pemprosesan audio/video masa nyata atau fizik dalam permainan.
  • Untuk pertukaran data yang ringkas atau pemindahan data yang besar, Transferable ArrayBuffer atau postMessage mungkin lebih mudah digunakan berbanding SharedArrayBuffer.

Kekangan dan Keselamatan

Untuk menggunakan SharedArrayBuffer dalam pelayar, pengasingan rentas asal diperlukan: tetapkan COOP kepada same-origin-allow-popups dan COEP kepada require-corp. SharedArrayBuffer akan dilumpuhkan jika keperluan ini tidak dipenuhi.

Petua Prestasi dan Penyahpepijatan

Operasi atomik (Atomics) adalah pantas, tetapi menunggu terlalu kerap dan penyelarasan berlebihan boleh meningkatkan kependaman.

Perkara berikut harus diperiksa untuk mengendalikan memori bersama dengan selamat dan cekap.

  • Paparan seperti Int32Array perlu dikendalikan dengan penjajaran byte yang betul.
  • Jelaskan indeks mana dalam buffer bersama yang digunakan oleh proses mana, dan kekalkan konvensyen yang konsisten dalam kod anda.
  • Jika anda melayan SharedArrayBuffer seperti ArrayBuffer biasa, perlumbaan data (data races) akan berlaku. Menulis ke indeks yang sama dari berbilang benang tanpa menggunakan Atomics adalah berbahaya.

Ringkasan

Dengan menggunakan SharedArrayBuffer dan Atomics dengan betul, anda boleh mencapai operasi memori bersama yang selamat dan pantas walaupun dalam TypeScript. Bermula dengan corak penyelarasan mudah seperti kaunter atau isyarat lebih mudah difahami, dan dengan mengurus penyelarasan serta indeks dengan teliti, anda boleh berkesan walaupun dalam senario kependaman rendah.

Anda boleh mengikuti artikel di atas menggunakan Visual Studio Code di saluran YouTube kami. Sila lihat juga saluran YouTube kami.

YouTube Video