UNPKG

atomics-sync

Version:

JavaScript multithreading synchronization library

210 lines (153 loc) 5.79 kB
## Atomics Sync Atomics Sync is lightweight library providing thread-safe synchronization primitives for JavaScript environments with shared memory support (Web Workers, Node.js worker_threads). Implements essential concurrency control mechanisms using SharedArrayBuffer and Atomics API. ### Features - [Mutex](https://github.com/slavamuravey/atomics-sync/blob/main/docs/classes/Mutex.md) - Mutual exclusion lock for critical sections - [SpinLock](https://github.com/slavamuravey/atomics-sync/blob/main/docs/classes/SpinLock.md) - Low-level busy-wait lock for very short operations - [Semaphore](https://github.com/slavamuravey/atomics-sync/blob/main/docs/classes/Semaphore.md) - Counting semaphore for resource management - [Condition](https://github.com/slavamuravey/atomics-sync/blob/main/docs/classes/Condition.md) - Condition variables for thread signaling - [Barrier](https://github.com/slavamuravey/atomics-sync/blob/main/docs/classes/Barrier.md) - Synchronization point for multiple threads - [Once](https://github.com/slavamuravey/atomics-sync/blob/main/docs/classes/Once.md) - One-time initialization primitive **Important**: For browsers, your server must send these headers: ``` Cross-Origin-Opener-Policy: same-origin Cross-Origin-Embedder-Policy: require-corp ``` ### Installation ```shell npm install atomics-sync ``` ### Why This Library? Modern JavaScript applications increasingly use: - Web Workers for parallel processing - SharedArrayBuffer for shared memory - CPU-intensive tasks (WASM, WebGL, etc.) These primitives help coordinate work between threads while preventing: - Race conditions - Data corruption - Deadlocks ### Usage Examples **Important**: There is no reliable way for a thread to know its own ID automatically in JavaScript environments. The parent/main thread must explicitly assign and pass a unique thread ID to each worker thread it creates. #### Mutex Init `mutex` to work safely with `shared` data: ```javascript const shared = new Int32Array( new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT) ); const mutex = Mutex.init(); ``` Pass `mutex` and `shared` to threads. Remember about `threadId`: ```javascript const worker = new Worker("./worker.js", { workerData: { threadId, shared, mutex } }); ``` Within thread use lock/unlock methods to wrap critical section: ```javascript try { Mutex.lock(mutex, threadId); // work with shared data here } finally { Mutex.unlock(mutex, threadId); } ``` See [full example](https://github.com/slavamuravey/atomics-sync/blob/main/example/mutex.mjs). #### Semaphore Here's a practical example demonstrating how to use a semaphore to make one thread wait for another thread to complete certain actions: Init semaphore: ```javascript const sem = Semaphore.init(0); ``` One thread creates another thread and must wait some initialization actions within it: ```javascript new Worker("./worker.js", { workerData: { sem } }); Semaphore.wait(sem); // continue execution // ... ``` Created thread performs necessary operations and notify parent thread: ```javascript // ... initSomeImportantThings(); Semaphore.post(sem); ``` See [full example](https://github.com/slavamuravey/atomics-sync/blob/main/example/semaphore.mjs). #### Condition Using a condition variable, we can make one thread wait for a change in a shared variable (protected by a mutex) before proceeding with its operation. Init condition variable and mutex, allocate shared variable: ```javascript const cond = Condition.init(); const mtx = Mutex.init(); const shared = new Int32Array( new SharedArrayBuffer(Int32Array.BYTES_PER_ELEMENT) ); shared[0] = -1; ``` One thread produces value: ```javascript Mutex.lock(mtx, threadId); shared[0] = Math.floor(Math.random() * 10); Condition.signal(cond); Mutex.unlock(mtx, threadId); ``` Another thread consumes the value and makes some work with it: ```javascript Mutex.lock(mtx, threadId); while (shared[0] < 0) { Condition.wait(cond, mtx, threadId); } shared[0] *= 10; Mutex.unlock(mtx, threadId); ``` See [full example](https://github.com/slavamuravey/atomics-sync/blob/main/example/condition.mjs). #### SpinLock A spinlock provides an interface nearly identical to a mutex (lock()/unlock()), but is optimized for very short wait times where spinning (busy-waiting) is more efficient than thread suspension. #### Barrier A barrier synchronizes multiple threads at a specific execution point. In this example, we launch 10 threads that execute at variable speeds and create a barrier with a count of 5. ```javascript // main.js const barrier = Barrier.init(5); for (let i = 0; i < 10; i++) { const threadId = i + 1; const worker = new Worker("./worker.js", { workerData: { threadId, barrier } }); } // worker.js setTimeout(() => { // ... Barrier.wait(barrier, threadId); // ... }, threadId * 100); ``` The first 5 threads to reach the barrier will block and wait. Once the 5th thread arrives, the barrier releases all waiting threads. The remaining 5 threads then proceed through the barrier in the same way. See [full example](https://github.com/slavamuravey/atomics-sync/blob/main/example/barrier.mjs). #### Once A Once primitive ensures one-time initialization in concurrent environments. Init once value: ```javascript const once = Once.init(); ``` Pass it into some threads: ```javascript const worker = new Worker("./worker.js", { workerData: { once } }); ``` Within thread: ```javascript Once.execute(once, () => { // some logic that should be executed only once }); ``` See [full example](https://github.com/slavamuravey/atomics-sync/blob/main/example/once.mjs). ### Documentation For complete API reference, see [API documentation](https://github.com/slavamuravey/atomics-sync/blob/main/docs/README.md).