dahlia-concurrency
Version:
High-level concurrency primitives and patterns for Node.js using worker_threads (queues, semaphore, mutex, worker pool, scheduler, and more)
144 lines (129 loc) • 3.31 kB
JavaScript
const { RwLock } = require("../primitives/rw-lock");
class ListNode {
constructor(element, priority) {
this.element = element;
this.priority = priority;
}
}
class ConcurrentPriorityQueue {
#values = [];
constructor(strategy = "max") {
this.readerBuffer = new SharedArrayBuffer(4);
this.writerBuffer = new SharedArrayBuffer(4);
this.rwLock = new RwLock(this.readerBuffer, this.writerBuffer, true);
if (strategy !== "max" && strategy !== "min") {
throw new Error("Invalid strategy: must be 'max' or 'min'");
}
this.strategy = strategy;
}
enqueue(item, priority) {
const newNode = new ListNode(item, priority);
this.rwLock.writeLock();
try {
this.#values.push(newNode);
this.#bubbleUp();
} catch (e) {
throw e;
} finally {
this.rwLock.writeUnlock();
}
}
dequeue() {
this.rwLock.writeLock();
try {
if (this.#values.length === 0) return null;
const max = this.#values[0];
const end = this.#values.pop();
if (this.#values.length > 0) {
this.#values[0] = end;
this.#sinkDown();
}
return max.element;
} catch (e) {
throw e;
} finally {
this.rwLock.writeUnlock();
}
}
peek() {
this.rwLock.readLock();
try {
return this.#values.length > 0 ? this.#values[0].element : null;
} catch (e) {
throw e;
} finally {
this.rwLock.readUnlock();
}
}
get size() {
this.rwLock.readLock();
try {
return this.#values.length;
} catch (e) {
throw e;
} finally {
this.rwLock.readUnlock();
}
}
isEmpty() {
this.rwLock.readLock();
try {
return this.#values.length === 0;
} catch (e) {
throw e;
} finally {
this.rwLock.readUnlock();
}
}
#bubbleUp() {
let idx = this.#values.length - 1;
const element = this.#values[idx];
while (idx > 0) {
let parentIdx = Math.floor((idx - 1) / 2);
let parent = this.#values[parentIdx];
if (this.#compare(parent, element)) break;
this.#values[parentIdx] = element;
this.#values[idx] = parent;
idx = parentIdx;
}
}
#compare(a, b) {
if (this.strategy === "max") {
return a.priority > b.priority;
} else {
// strategy === "min"
return a.priority < b.priority;
}
}
#sinkDown() {
let idx = 0;
const length = this.#values.length;
const element = this.#values[0];
while (true) {
let leftChildIdx = 2 * idx + 1;
let rightChildIdx = 2 * idx + 2;
let leftChild, rightChild;
let swap = null;
if (leftChildIdx < length) {
leftChild = this.#values[leftChildIdx];
if (!this.#compare(element, leftChild)) {
swap = leftChildIdx;
}
}
if (rightChildIdx < length) {
rightChild = this.#values[rightChildIdx];
if (
(swap === null && !this.#compare(element, rightChild)) ||
(swap !== null && !this.#compare(leftChild, rightChild))
) {
swap = rightChildIdx;
}
}
if (swap === null) break;
this.#values[idx] = this.#values[swap];
this.#values[swap] = element;
idx = swap;
}
}
}
module.exports = { ConcurrentPriorityQueue };