atomics-sync
Version:
JavaScript multithreading synchronization library
105 lines (88 loc) • 3.46 kB
text/typescript
import { Condition } from "./condition";
import { InvalidError } from "./errors";
import { Mutex } from "./mutex";
const { store, load, add } = Atomics;
/**
* Barrier object containing shared memory structures for synchronization
* barrier - stores barrier state (thread count, waited count, and generation)
* mutex - mutex for protecting barrier access
* cond - condition variable for threads to wait on
*/
export interface BarrierObject {
barrier: BigInt64Array<SharedArrayBuffer>;
mutex: Int32Array<SharedArrayBuffer>;
cond: Int32Array<SharedArrayBuffer>;
}
/**
* A synchronization primitive that enables multiple threads to wait for each other
* to reach a common execution point before continuing.
*
* Implements a reusable barrier using shared memory, mutex and condition variable.
*/
export class Barrier {
// Indexes for accessing different values in the barrier array
private static readonly INDEX_COUNT = 0; // Stores total threads required
private static readonly INDEX_WAITED = 1; // Stores number of threads currently waiting
private static readonly INDEX_GENERATION = 2; // Stores current barrier generation
/**
* Initializes a new barrier with the specified thread count
* @param count Number of threads that must reach the barrier before continuing
* @returns Initialized BarrierObject with shared structures
* @throws {InvalidError} If count is not an integer
* @throws {RangeError} If count is <= 0
*/
static init(count: number): BarrierObject {
Barrier.validateCount(count);
const barrier = new BigInt64Array(new SharedArrayBuffer(BigInt64Array.BYTES_PER_ELEMENT * 3));
store(barrier, Barrier.INDEX_COUNT, BigInt(count));
store(barrier, Barrier.INDEX_WAITED, 0n);
store(barrier, Barrier.INDEX_GENERATION, 0n);
const mutex = Mutex.init();
const cond = Condition.init();
return {
barrier,
mutex,
cond
};
}
/**
* Makes the calling thread wait at the barrier until all threads have arrived
* @param barrier The barrier object to wait on
* @param threadId Unique identifier for the calling thread
* @returns true if this thread was the last to arrive (releases others), false otherwise
*/
static wait(barrier: BarrierObject, threadId: number) {
Mutex.lock(barrier.mutex, threadId);
const generation = load(barrier.barrier, Barrier.INDEX_GENERATION);
const count = load(barrier.barrier, Barrier.INDEX_COUNT);
const waited = add(barrier.barrier, Barrier.INDEX_WAITED, 1n) + 1n;
try {
if (waited >= count) {
store(barrier.barrier, Barrier.INDEX_WAITED, 0n);
add(barrier.barrier, Barrier.INDEX_GENERATION, 1n);
Condition.broadcast(barrier.cond);
return true;
}
while (load(barrier.barrier, Barrier.INDEX_GENERATION) === generation) {
Condition.wait(barrier.cond, barrier.mutex, threadId);
}
return false;
} finally {
Mutex.unlock(barrier.mutex, threadId);
}
}
/**
* Validates that the thread count is a positive integer
* @param count Number to validate
* @throws {InvalidError} If count is not an integer
* @throws {RangeError} If count is <= 0
*/
private static validateCount(count: number) {
if (!Number.isInteger(count)) {
throw new InvalidError("count should be integer");
}
if (count <= 0) {
throw new RangeError("count should be greater zero");
}
}
}