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 בערוץ היוטיוב שלנו. נא לבדוק גם את ערוץ היוטיוב.