SharedArrayBuffer בג'אווהסקריפט

SharedArrayBuffer בג'אווהסקריפט

מאמר זה מסביר את SharedArrayBuffer בג'אווהסקריפט.

נספק הסבר מפורט על הבסיסים של SharedArrayBuffer, כיצד להשתמש בו, מקרי שימוש ספציפיים ושיקולי אבטחה.

YouTube Video

SharedArrayBuffer בג'אווהסקריפט

SharedArrayBuffer הוא כלי עוצמתי בג'אווהסקריפט לשיתוף זיכרון בין מספר תהליכים. במיוחד בשילוב עם Web Workers, הוא מאפשר עיבוד מקבילי, מה שהופך אותו ליעיל במשימות חישוביות אינטנסיביות וביישומים הדורשים יכולות בזמן אמת.

מהו SharedArrayBuffer?

SharedArrayBuffer מספק אזור זיכרון בג'אווהסקריפט שמאפשר לשתף נתונים בינאריים בין מספר תהליכים (בעיקר Web Workers). ArrayBuffer רגיל דורש העתקה בין התהליך הראשי ל-Web Workers, אבל SharedArrayBuffer מאפשר שיתוף זיכרון ישיר ללא העתקה, ובכך משפר משמעותית את הביצועים.

תכונות

  • זיכרון משותף זה מאפשר לל־חוטים (threads) מרובים לעבוד עם אותו מרחב זיכרון.
  • שיפור ביצועים מכיוון שאפשר לוותר על העתקה, העומס פוחת כאשר מעבדים כמויות נתונים גדולות.
  • סנכרון תהליכונים (Threads) ניתן להשתמש בו יחד עם Atomics כדי לבצע סנכרון ולמנוע התנגשויות בגישה לזיכרון.

דוגמה בסיסית לשימוש

 1// Create a 16-byte shared memory
 2const sharedBuffer = new SharedArrayBuffer(16);
 3
 4// Treat it as an Int32Array
 5const sharedArray = new Int32Array(sharedBuffer);
 6
 7// Set a value
 8sharedArray[0] = 42;
 9
10console.log(sharedArray[0]);  // 42

בדוגמה זו ניצור אזור זיכרון בגודל 16 בתים באמצעות SharedArrayBuffer ונטפל באזור הזיכרון הזה כ-Int32Array. אזור זיכרון זה יכול להיות משותף בין מספר תהליכים.

שימוש עם Web Workers

הערך האמיתי של SharedArrayBuffer מתגלה בשימוש בשילוב עם Web Workers. הקוד הבא הוא דוגמה לשימוש בזיכרון משותף בין התהליך הראשי לעובד.

בתהליך הראשי

 1// Create a shared buffer
 2const sharedBuffer = new SharedArrayBuffer(16);
 3const sharedArray = new Int32Array(sharedBuffer);
 4
 5// Create a worker
 6const worker = new Worker('worker.js');
 7
 8// Pass the shared buffer to the worker
 9worker.postMessage(sharedBuffer);
10
11// Modify the memory
12// Output : Main thread: 100
13sharedArray[0] = 100;
14console.log("Main thread: ", sharedArray[0]);

בצד העובד (worker.js)

 1// worker.js
 2self.onmessage = function(event) {
 3    // Use the received shared buffer
 4    const sharedArray = new Int32Array(event.data);
 5
 6    // Read the contents of the memory
 7    // Output : Worker thread: 100
 8    console.log("Worker thread: ", sharedArray[0]);
 9
10    // Change the value
11    sharedArray[0] = 200;
12};
  • בדוגמה זו, התהליך הראשי יוצר מאגר משותף ומעביר אותו לעובד. העובד יכול לגשת למאגר זה כדי לקרוא ולשנות ערכים. בדרך זו ניתן לשתף נתונים בין תהליכים ללא העתקה.

אישור עדכון דו־כיווני

באמצעות שימוש ב־SharedArrayBuffer, גם התהליך הראשי וגם העובדים (workers) יכולים לקרוא ולכתוב של אותו זיכרון, דבר שמאפשר אישור עדכון דו־כיווני. להלן דוגמה שבה התהליך הראשי קובע ערך מסוים, העובד משנה ערך זה, ואז התהליך הראשי בודק אם התרחש עדכון.

בתהליך הראשי

 1// Create a shared buffer
 2const sharedBuffer = new SharedArrayBuffer(16);
 3const sharedArray = new Int32Array(sharedBuffer);
 4
 5// Create a worker
 6const worker = new Worker('worker.js');
 7
 8// Pass the shared buffer to the worker
 9worker.postMessage(sharedBuffer);
10
11// Set initial value
12// Output : Main thread initial: 100
13sharedArray[0] = 100;
14console.log("Main thread initial:", sharedArray[0]);
15
16// Listen for worker confirmation
17worker.onmessage = () => {
18    // Output : Main thread after worker update: 200
19    console.log("Main thread after worker update:", sharedArray[0]);
20};

בצד העובד (worker.js)

 1// worker.js
 2self.onmessage = function(event) {
 3    const sharedArray = new Int32Array(event.data);
 4
 5    // Read initial value
 6    // Output : Worker thread received: 100
 7    console.log("Worker thread received:", sharedArray[0]);
 8
 9    // Update the value
10    sharedArray[0] = 200;
11
12    // Notify main thread
13    self.postMessage("Value updated");
14};
  • בדוגמה זו, התהליך הראשי כותב את הערך 100 תחילה, ולאחר שהעובד קורא אותו, הוא משנה אותו ל־200. לאחר מכן, העובד מעדכן את התהליך הראשי, והתהליך הראשי קורא שוב את הזיכרון המשותף כדי לוודא שהערך התעדכן. באופן זה, שילוב של התראות מאפשר אישור עדכון דו־כיווני.

סינכרון עם Atomics

בעת שימוש בזיכרון משותף, יש להיזהר ממצבי אשמת מידע ומחוסר עקביות. כאשר מספר תהליכונים ניגשים לאותה הזיכרון בו-זמנית, יכולים להתרחש עימותים. כדי למנוע זאת, JavaScript משתמשת באובייקט Atomics לסינכרון.

לדוגמה, כדי להוסיף בצורה בטוחה למונה עם מספר תהליכונים, ניתן להשתמש ב-Atomics למניעת עימותים.

1const sharedBuffer = new SharedArrayBuffer(16);
2const sharedArray = new Int32Array(sharedBuffer);
3
4// Increment the counter
5Atomics.add(sharedArray, 0, 1);
6
7console.log(Atomics.load(sharedArray, 0));  // 1

Atomics.add מוסיף לערך במיקום ספציפי בצורה אטומית ומחזיר את הערך החדש. פעולה זו מובטחת להיות ללא עימותים עם תהליכונים אחרים. גם Atomics.load משמש לקריאת ערכים בצורה בטוחה מזיכרון משותף.

המתנה והתראה באמצעות Atomics.wait ו־Atomics.notify

בעת שימוש ב־SharedArrayBuffer, ישנם מצבים שבהם עובד צריך להמתין עד שתנאי מסוים מתקיים, ואז כאשר עובד אחר ממלא תנאי זה — עליו להודיע לעובד הממתין. במקרים כאלה, פונקציות Atomics.wait ו־Atomics.notify שימושיות.

Atomics.wait חוסם תהליך עד שהערך באינדקס מסוים בזיכרון המשותף משתנה, בעוד ש־Atomics.notify מודיע לתהליכים הממתינים שניתן להמשיך. דבר זה מאפשר המתנה והתראה בטוחות בין עובדים מרובים. עם זאת, אין אפשרות להשתמש ב־Atomics.wait בחוט הראשי והוא זמין רק בתוך עובדים (workers).

 1// Create a shared buffer (1 Int32 slot is enough for signaling)
 2const sharedBuffer = new SharedArrayBuffer(4);
 3const sharedArray = new Int32Array(sharedBuffer);
 4
 5// Create workers with names
 6const waiter = new Worker('worker.js', { name: 'waiter' });
 7const notifier = new Worker('worker.js', { name: 'notifier' });
 8
 9// Pass the shared buffer to both
10waiter.postMessage(sharedBuffer);
11notifier.postMessage(sharedBuffer);
12
13// Listen for messages
14waiter.onmessage = (event) => {
15    console.log(`[Main] Message from waiter:`, event.data);
16};
17notifier.onmessage = (event) => {
18    console.log(`[Main] Message from notifier:`, event.data);
19};
  • בחוט הראשי, נוצר SharedArrayBuffer כזיכרון משותף והוא מומר ל־Int32Array עם איבר אחד בלבד. תא המספריים היחיד הזה משמש כאות לסנכרון בין העובדים (workers). לאחר מכן, נוצרים שני עובדים, ולכל אחד מהם מוקצה תפקיד באמצעות המאפיין name: waiter (תפקיד הממתין) ו־notifier (תפקיד המתריע). לבסוף, הזיכרון המשותף מועבר לשני העובדים, ומותקנים מאזיני onmessage כדי שניתן יהיה לקבל הודעות מכל עובד.

בצד העובד (worker.js)

 1// worker.js
 2onmessage = (event) => {
 3    const sharedArray = new Int32Array(event.data);
 4
 5    if (self.name === 'waiter') {
 6        postMessage('Waiter is waiting...');
 7        // Wait until notifier signals index 0
 8        Atomics.wait(sharedArray, 0, 0);
 9        postMessage('Waiter was notified!');
10    }
11
12    if (self.name === 'notifier') {
13        postMessage('Notifier is preparing...');
14        setTimeout(() => {
15            // Notify waiter after 2 seconds
16            Atomics.store(sharedArray, 0, 1);
17            Atomics.notify(sharedArray, 0, 1);
18            postMessage('Notifier has sent the signal!');
19        }, 2000);
20    }
21};
22// Output
23// [Main] Message from waiter: Waiter is waiting...
24// [Main] Message from notifier: Notifier is preparing...
25// [Main] Message from notifier: Notifier has sent the signal!
26// [Main] Message from waiter: Waiter was notified!
  • בדוגמה זו, העובד waiter נשאר במצב המתנה באמצעות Atomics.wait כל עוד הערך באינדקס 0 הוא 0. לעומת זאת, כאשר העובד notifier משנה את הערך ל־123 באמצעות Atomics.store וקורא ל־Atomics.notify, העובד waiter יתחדש בפעולה ויוכל לקבל את הערך המעודכן. באמצעות זאת, ניתן להשיג המתנה והתראה יעילות ובטוחות בין חוטים (threads).

שימושים נפוצים עבור SharedArrayBuffer

SharedArrayBuffer מועיל במיוחד עבור המקרים הבאים:.

  • עיבוד בזמן אמת מתאים ליישומים הדורשים השהיה נמוכה, כמו עיבוד אודיו, וידאו או מנועי משחקים — בהם יש לשתף נתונים מיידית בין תהליכונים.
  • חישוב מקבילי בעיבוד כמות גדולה של נתונים בו־זמנית עם מספר תהליכים, שימוש ב־SharedArrayBuffer מונע העתקת זיכרון ויכול לשפר את הביצועים.
  • למידת מכונה על ידי הרצה מקבילית של משימות כגון קדם־עיבוד נתונים ואינפרנס, ניתן להגיע לחישוב יעיל יותר.

שיקולי אבטחה

SharedArrayBuffer הוא כלי חזק, אך יש בו גם סיכוני אבטחה. במיוחד, חששות מהתקפות ערוץ צד כמו Spectre עצרו באופן זמני את התמיכה בו. כדי לצמצם את הפגיעות הזו, דפדפנים יישמו את הצעדים הבאים:.

  • הפרדת אתרים (Site Isolation) אתרים המאפשרים שימוש ב־SharedArrayBuffer ירוצו בתהליך מבודד לחלוטין מאתרים אחרים.
  • מדיניות משאבים חוצת־מקור (Cross-Origin Resource Policy) כדי להשתמש ב־SharedArrayBuffer, חובה להגדיר כראוי את כותרות Cross-Origin-Opener-Policy ו־Cross-Origin-Embedder-Policy.

לדוגמה, על ידי הגדרת כותרות כמו הבאות, השימוש ב-SharedArrayBuffer הופך לאפשרי:.

1Cross-Origin-Opener-Policy: same-origin
2Cross-Origin-Embedder-Policy: require-corp

זה מונע ממשאבים חיצוניים להתערב בתוכן הנוכחי ומגביר את האבטחה.

סיכום

SharedArrayBuffer הוא כלי רב עוצמה לשיתוף זיכרון בין מספר תהליכונים. זוהי טכנולוגיה חיונית לשיפור ביצועים, והשפעותיה ניכרות במיוחד בתחומי העיבוד בזמן אמת והחישוב המקבילי. עם זאת, זה כולל גם סיכוני אבטחה, ולכן תצורה נכונה וסינכרון חשובים.

באמצעות שימוש ב־SharedArrayBuffer, ניתן לבנות יישומי ווב מתקדמים ויעילים יותר.

תוכלו לעקוב אחר המאמר שלמעלה באמצעות Visual Studio Code בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.

YouTube Video