diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 8d806a974479a42869b5bcdcecb7b89f51ae9e2e..d4f0cf392d36822bb238fc6cff1f290f3935ff2a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -221,6 +221,14 @@ clang-tidy:
   variables:
     EMPER_WS_QUEUE_SCHEDULER: "cl2"
 
+.ws-queue-scheduler-cl3:
+  variables:
+    EMPER_WS_QUEUE_SCHEDULER: "cl3"
+
+.ws-queue-scheduler-cl4:
+  variables:
+    EMPER_WS_QUEUE_SCHEDULER: "cl4"
+
 .waitfree-ws:
   variables:
     EMPER_WAITFREE_WORK_STEALING: "true"
@@ -349,6 +357,16 @@ test-ws-queue-scheduler-cl2:
     - .test
     - .ws-queue-scheduler-cl2
 
+test-ws-queue-scheduler-cl3:
+  extends:
+    - .test
+    - .ws-queue-scheduler-cl3
+
+test-ws-queue-scheduler-cl3:
+  extends:
+    - .test
+    - .ws-queue-scheduler-cl3
+
 test-waitfree-ws:
   extends:
     - .test
diff --git a/README.md b/README.md
index ccdd3ad739d9a25bc6036741005fe7badded083d..aa7414bd86b7e9ad76b60b080d5feba92d80703f 100644
--- a/README.md
+++ b/README.md
@@ -75,4 +75,36 @@ url: https://www4.cs.fau.de/~flow/papers/schmaus2021nowa.pdf
 [pfeiffer2020cactus]
 Pfeiffer, Nicolas. A Wait-Free Cactus Stack Implementation for a Microparalelism
 Runtime. Master's thesis. MA-I4-2020-02. Mar. 2, 2020.
-url:  https://www4.cs.fau.de/~flow/papers/pfeiffer2020cactus.pdf
+url: https://www4.cs.fau.de/~flow/papers/pfeiffer2020cactus.pdf
+
+# Literature
+
+> The dwarf sees farther than the giant, when he has the giant's shoulder to mount on.
+  - Samuel Taylor Coleridge, The Friend (1828)
+
+EMPER uses concepts, ideas and algorithms from the following
+publications. You will find the key of a publication, e.g.,
+[chase2005dynamic] sometimes mentioned in EMPER's source code.
+
+[chase2005dynamic]
+Chase, David and Yossi Lev. “Dynamic Circular Work-Stealing Deque”. In: Pro-
+ceedings of the Seventeenth Annual ACM Symposium on Parallelism in Algorithms
+and Architectures. SPAA ’05. Las Vegas, Nevada, USA: Association
+for Computing Machinery, 2005, pp. 21–28. isbn: 1581139861. doi: [10.1145/1073970.1073974](https://doi.org/10.1145/1073970.1073974).
+
+[le2013correct]
+Lê, Nhat Minh, Antoniu Pop, Albert Cohen, and Francesco Zappa Nardelli.
+“Correct and Efficient Work-Stealing for Weak Memory Models”. In: Proceedings
+of the 18th ACM SIGPLAN Symposium on Principles and Practice of
+Parallel Programming. PPoPP ’13. Shenzhen, China: Association for Computing
+Machinery, 2013, pp. 69–80. isbn: 9781450319225. doi: 10.1145/2442516.2442524.
+url: https://hal.inria.fr/hal-00802885/document
+
+[norris2013cdschecker]
+Norris, Brian and Brian Demsky. “CDSchecker: Checking Concurrent Data Structures
+Written with C/C++ Atomics”. In: Proceedings of the 2013 ACM SIGPLAN
+International Conference on Object Oriented Programming Systems
+Languages Applications. OOPSLA ’13. Indianapolis, Indiana, USA: Association
+for Computing Machinery, 2013, pp. 131–150. isbn: 9781450323741.
+doi: 10.1145/2509136.2509514.
+url: http://plrg.eecs.uci.edu/publications/c11modelcheck.pdf
diff --git a/emper/lib/adt/PushBottomResult.hpp b/emper/lib/adt/PushBottomResult.hpp
index b65236ce624f3b41d807c774787d81a0bcdc27bb..c61eddcff6de8c41bc79a2d6ae15c9ac6d7905e0 100644
--- a/emper/lib/adt/PushBottomResult.hpp
+++ b/emper/lib/adt/PushBottomResult.hpp
@@ -2,6 +2,8 @@
 // Copyright © 2022 Florian Schmaus
 #pragma once
 
+#include <cstdint>
+
 namespace adt {
 
 struct PushBottomResult {
diff --git a/emper/lib/adt/WsClv3Queue.hpp b/emper/lib/adt/WsClv3Queue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..33e1a38982575b92e365139fce9258e50fd390a0
--- /dev/null
+++ b/emper/lib/adt/WsClv3Queue.hpp
@@ -0,0 +1,154 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2020-2022 Florian Schmaus
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+
+#include "Common.hpp"
+#include "StealingResult.hpp"
+#include "emper-common.h"
+#include "lib/adt/PushBottomResult.hpp"
+
+namespace adt {
+
+/**
+ * @file
+ *
+ * A CL queue used for work stealing.
+ *
+ * Implements the partially concurrent queue for the work stealing
+ * scheduling algorithm as defined in "Dynamic Circular Work-Stealing Deque" by Chase and Lev
+ * [chase2005dynamic]. The worker uses pushBottom to push new items on the stack, and retrieves
+ * items via popBottom. Eventually, when its own queue is empty, it will use popTop to retrieve
+ * items from a different Worker's queue.
+ *
+ * Only pushBottom/popBottom and popTop may be used concurrently,
+ * i.e. concurrent use of pushBottom with popBottom is not allowed.
+ *
+ * Unlike the ABP queue, the CL queue uses a circular array and
+ * only-incrementing uint64_t bottom and top fields. According to the
+ * authors "A 64-bit integer is large enough to accommodate 64 years
+ * of pushes, pops and steals executing at a rate of 4 billtion
+ * operations per second", so overflows should be no problem.
+ */
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+class WsClv3Queue {
+	ALIGN_TO_CACHE_LINE std::atomic<uint64_t> bottom;
+	ALIGN_TO_CACHE_LINE std::atomic<uint64_t> top;
+	// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+	PAYLOAD queue[CAPACITY];
+
+ public:
+	// 'bottom' and 'top' are initialized to '1', instead of '0' as
+	// it's done in the "Dynamic Circular Work-Stealing Deque" paper
+	// because popBottom will decrement bottom, which will result in
+	// an underflow if bottom is '0'. The paper's queue uses Java
+	// 'long' for bottom and top and is thus safe since it's signed.
+	WsClv3Queue() : bottom(1), top(1) {}
+	// TODO: Decide what to do regarding the following suppressed lint.
+	// NOLINTNEXTLINE(readability-avoid-const-params-in-decls)
+	auto pushBottom(const PAYLOAD item) -> PushBottomResult;
+	template <const int maxRetries>
+	auto popTop(PAYLOAD *item) -> emper::StealingResult;
+	auto popBottom(PAYLOAD *item) -> bool;
+	[[nodiscard]] auto isFull() const -> bool;
+	[[nodiscard]] auto isEmpty() const -> bool;
+	void print() const;
+	[[nodiscard]] inline auto capacity() const -> uintptr_t { return CAPACITY; }
+	[[nodiscard]] inline auto usedSlots() const -> uint64_t { return bottom - top; }
+	[[nodiscard]] inline auto freeSlots() const -> uintptr_t { return capacity() - usedSlots(); }
+};
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv3Queue<PAYLOAD, CAPACITY>::pushBottom(const PAYLOAD item) -> PushBottomResult {
+	bool pushed = true;
+	uint64_t localBottom = bottom.load(std::memory_order_relaxed);
+	uint64_t localTop = top.load(std::memory_order_relaxed);
+
+	uint64_t currentSize = localBottom - localTop;
+	if (currentSize >= CAPACITY) {
+		assert(localBottom == localTop);
+		pushed = false;
+		goto out;
+	}
+
+	queue[bottom % CAPACITY] = item;
+	bottom.store(localBottom + 1, std::memory_order_release);
+
+out:
+	return PushBottomResult{pushed, pushed ? currentSize + 1 : currentSize};
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+template <const int maxRetries>
+auto WsClv3Queue<PAYLOAD, CAPACITY>::popTop(PAYLOAD *item) -> emper::StealingResult {
+	int retries = 0;
+
+	uint64_t localTop = top.load(std::memory_order_relaxed);
+
+loop:
+	ATTR_UNUSED;	// to suppress unused label 'loop' warning if maxRetries == 0.
+	uint64_t localBottom = bottom.load(std::memory_order_acquire);
+
+	if (localBottom <= localTop) return emper::StealingResult::Empty;
+
+	*item = queue[localTop % CAPACITY];
+
+	// If cas fails, then this popTop() lost the race against another
+	// popTop().
+	if (top.compare_exchange_strong(localTop, localTop + 1, std::memory_order_release,
+																	std::memory_order_acquire))
+		return emper::StealingResult::Stolen;
+
+	// Loop indefinitely
+	if constexpr (maxRetries < 0) goto loop;
+	// Loop maxRetries times
+	if constexpr (maxRetries > 0) {
+		if (retries == maxRetries) return emper::StealingResult::LostRace;
+
+		++retries;
+	}
+
+	return emper::StealingResult::LostRace;
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv3Queue<PAYLOAD, CAPACITY>::popBottom(PAYLOAD *item) -> bool {
+	uint64_t localBottom = bottom.fetch_sub(1, std::memory_order_acq_rel) - 1;
+	uint64_t localTop = top.load(std::memory_order_acquire);
+
+	if (localTop > localBottom) {
+		// The queue is empty.
+		bottom = localTop;
+		return false;
+	}
+
+	*item = queue[localBottom % CAPACITY];
+	if (localBottom > localTop) return true;
+
+	bool res = top.compare_exchange_weak(localTop, localTop + 1, std::memory_order_release,
+																			 std::memory_order_relaxed);
+
+	// Either a popTop() removed the element ('res' is false) or we
+	// removed the element ('res' is true), but we need to increment
+	// the 'bottom' value, since the element bottom pointed at is now
+	// gone. N.B. bottom does point to the next free slot, the actual
+	// element we remove is bottom-1.
+	bottom.store(localBottom + 1, std::memory_order_relaxed);
+
+	return res;
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv3Queue<PAYLOAD, CAPACITY>::isFull() const -> bool {
+	return usedSlots() >= CAPACITY;
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv3Queue<PAYLOAD, CAPACITY>::isEmpty() const -> bool {
+	return top >= bottom;
+}
+
+}	 // namespace adt
diff --git a/emper/lib/adt/WsClv4Queue.hpp b/emper/lib/adt/WsClv4Queue.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..987c5b49ec1e0d0999dd98da495ad8ffd2e540e8
--- /dev/null
+++ b/emper/lib/adt/WsClv4Queue.hpp
@@ -0,0 +1,164 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2020-2022 Florian Schmaus
+#pragma once
+
+#include <atomic>
+#include <cassert>
+#include <cstdint>
+
+#include "Common.hpp"
+#include "StealingResult.hpp"
+#include "emper-common.h"
+#include "lib/adt/PushBottomResult.hpp"
+
+namespace adt {
+
+/**
+ * @file
+ *
+ * A CL queue used for work stealing.
+ *
+ * Implements the partially concurrent queue for the work stealing
+ * scheduling algorithm as defined in "Dynamic Circular Work-Stealing Deque" by Chase and Lev
+ * [chase2005dynamic]. The worker uses pushBottom to push new items on the stack, and retrieves
+ * items via popBottom. Eventually, when its own queue is empty, it will use popTop to retrieve
+ * items from a different Worker's queue.
+ *
+ * Only pushBottom/popBottom and popTop may be used concurrently,
+ * i.e. concurrent use of pushBottom with popBottom is not allowed.
+ *
+ * Unlike the ABP queue, the CL queue uses a circular array and
+ * only-incrementing uint64_t bottom and top fields. According to the
+ * authors "A 64-bit integer is large enough to accommodate 64 years
+ * of pushes, pops and steals executing at a rate of 4 billtion
+ * operations per second", so overflows should be no problem.
+ *
+ * This also incoroperates the findings from the paper "Correct and
+ * Efficient Work-Stealing for Weak Memory Models" by Lê et
+ * al. [le2013correct]. Since this queue is not dynamically resized
+ * (yet), the bug in the implementation of this paper, found by Norris
+ * and Demsky [norris2013cdschecker], does not exist.
+ */
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+class WsClv4Queue {
+	ALIGN_TO_CACHE_LINE std::atomic<uint64_t> bottom;
+	ALIGN_TO_CACHE_LINE std::atomic<uint64_t> top;
+	// NOLINTNEXTLINE(modernize-avoid-c-arrays)
+	PAYLOAD queue[CAPACITY];
+
+ public:
+	// 'bottom' and 'top' are initialized to '1', instead of '0' as
+	// it's done in the "Dynamic Circular Work-Stealing Deque" paper
+	// because popBottom will decrement bottom, which will result in
+	// an underflow if bottom is '0'. The paper's queue uses Java
+	// 'long' for bottom and top and is thus safe since it's signed.
+	WsClv4Queue() : bottom(1), top(1) {}
+	// TODO: Decide what to do regarding the following suppressed lint.
+	// NOLINTNEXTLINE(readability-avoid-const-params-in-decls)
+	auto pushBottom(const PAYLOAD item) -> PushBottomResult;
+	template <const int maxRetries>
+	auto popTop(PAYLOAD *item) -> emper::StealingResult;
+	auto popBottom(PAYLOAD *item) -> bool;
+	[[nodiscard]] auto isFull() const -> bool;
+	[[nodiscard]] auto isEmpty() const -> bool;
+	void print() const;
+	[[nodiscard]] inline auto capacity() const -> uintptr_t { return CAPACITY; }
+	[[nodiscard]] inline auto usedSlots() const -> uint64_t { return bottom - top; }
+	[[nodiscard]] inline auto freeSlots() const -> uintptr_t { return capacity() - usedSlots(); }
+};
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv4Queue<PAYLOAD, CAPACITY>::pushBottom(const PAYLOAD item) -> PushBottomResult {
+	bool pushed = true;
+	uint64_t localTop = top.load(std::memory_order_relaxed);
+	std::atomic_thread_fence(std::memory_order_seq_cst);
+	uint64_t localBottom = bottom.load(std::memory_order_relaxed);
+
+	uint64_t currentSize = localBottom - localTop;
+	if (currentSize >= CAPACITY) {
+		assert(localBottom == localTop);
+		pushed = false;
+		goto out;
+	}
+
+	queue[bottom % CAPACITY] = item;
+	std::atomic_thread_fence(std::memory_order_release);
+	bottom.store(localBottom + 1, std::memory_order_relaxed);
+
+out:
+	return PushBottomResult{pushed, pushed ? currentSize + 1 : currentSize};
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+template <const int maxRetries>
+auto WsClv4Queue<PAYLOAD, CAPACITY>::popTop(PAYLOAD *item) -> emper::StealingResult {
+	int retries = 0;
+
+loop:
+	ATTR_UNUSED;	// to suppress unused label 'loop' warning if maxRetries == 0.
+	uint64_t localTop = top.load(std::memory_order_acquire);
+	std::atomic_thread_fence(std::memory_order_seq_cst);
+	uint64_t localBottom = bottom.load(std::memory_order_acquire);
+
+	if (localBottom <= localTop) return emper::StealingResult::Empty;
+
+	*item = queue[localTop % CAPACITY];
+
+	// If cas fails, then this popTop() lost the race against another
+	// popTop().
+	if (top.compare_exchange_strong(localTop, localTop + 1, std::memory_order_seq_cst,
+																	std::memory_order_relaxed))
+		return emper::StealingResult::Stolen;
+
+	// Loop indefinitely
+	if constexpr (maxRetries < 0) goto loop;
+	// Loop maxRetries times
+	if constexpr (maxRetries > 0) {
+		if (retries == maxRetries) return emper::StealingResult::LostRace;
+
+		++retries;
+	}
+
+	return emper::StealingResult::LostRace;
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv4Queue<PAYLOAD, CAPACITY>::popBottom(PAYLOAD *item) -> bool {
+	uint64_t localBottom = bottom.load(std::memory_order_relaxed) - 1;
+	bottom.store(localBottom, std::memory_order_relaxed);
+	std::atomic_thread_fence(std::memory_order_seq_cst);
+	uint64_t localTop = top.load(std::memory_order_acquire);
+
+	if (localTop > localBottom) {
+		// The queue is empty.
+		bottom.store(localBottom + 1, std::memory_order_relaxed);
+		return false;
+	}
+
+	*item = queue[localBottom % CAPACITY];
+	if (localBottom > localTop) return true;
+
+	bool res = top.compare_exchange_weak(localTop, localTop + 1, std::memory_order_seq_cst,
+																			 std::memory_order_relaxed);
+
+	// Either a popTop() removed the element ('res' is false) or we
+	// removed the element ('res' is true), but we need to increment
+	// the 'bottom' value, since the element bottom pointed at is now
+	// gone. N.B. bottom does point to the next free slot, the actual
+	// element we remove is bottom-1.
+	bottom.store(localBottom + 1, std::memory_order_relaxed);
+
+	return res;
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv4Queue<PAYLOAD, CAPACITY>::isFull() const -> bool {
+	return usedSlots() >= CAPACITY;
+}
+
+template <typename PAYLOAD, const uintptr_t CAPACITY>
+auto WsClv4Queue<PAYLOAD, CAPACITY>::isEmpty() const -> bool {
+	return top >= bottom;
+}
+
+}	 // namespace adt
diff --git a/emper/lib/adt/WsQueue.hpp b/emper/lib/adt/WsQueue.hpp
index 44edaebe0bd58b9878f6ab4199430a9f1bfb0801..53daafdc3115c457ac3b271af3216e1b18499866 100644
--- a/emper/lib/adt/WsQueue.hpp
+++ b/emper/lib/adt/WsQueue.hpp
@@ -16,6 +16,14 @@
 #include "lib/adt/WsClV2Queue.hpp"
 #endif
 
+#ifdef EMPER_LIB_ADT_WSQUEUE_INCLUDE_CL3_QUEUE
+#include "lib/adt/WsClv3Queue.hpp"
+#endif
+
+#ifdef EMPER_LIB_ADT_WSQUEUE_INCLUDE_CL4_QUEUE
+#include "lib/adt/WsClv4Queue.hpp"
+#endif
+
 namespace lib::adt::wsqueue {
 
 template <typename PAYLOAD, const uintptr_t CAPACITY>
diff --git a/emper/lib/adt/meson.build b/emper/lib/adt/meson.build
index e0b853fe2a18472d25d80130820da51f7866832e..c96975ce9be62d301e39a62585a6cfb472efe0c3 100644
--- a/emper/lib/adt/meson.build
+++ b/emper/lib/adt/meson.build
@@ -1,6 +1,8 @@
 lib_adt_wsqueue_include_locked_queue = false
 lib_adt_wsqueue_include_cl_queue = false
 lib_adt_wsqueue_include_cl2_queue = false
+lib_adt_wsqueue_include_cl3_queue = false
+lib_adt_wsqueue_include_cl4_queue = false
 
 
 if ws_queue_default == 'locked'
@@ -12,6 +14,12 @@ elif ws_queue_default == 'cl'
 elif ws_queue_default == 'cl2'
   lib_adt_wsqueue_include_cl2_queue = true
   ws_queue_default_type = '::adt::WsClV2Queue'
+elif ws_queue_default == 'cl3'
+  lib_adt_wsqueue_include_cl3_queue = true
+  ws_queue_default_type = '::adt::WsClv3Queue'
+elif ws_queue_default == 'cl4'
+  lib_adt_wsqueue_include_cl4_queue = true
+  ws_queue_default_type = '::adt::WsClv4Queue'
 else
   error('Unknown setting for ws_queue_default: ' + ws_queue_default)
 endif
@@ -30,6 +38,12 @@ elif ws_queue_scheduler == 'cl'
 elif ws_queue_scheduler == 'cl2'
   lib_adt_wsqueue_include_cl2_queue = true
   ws_queue_scheduler_type = '::adt::WsClV2Queue'
+elif ws_queue_scheduler == 'cl3'
+  lib_adt_wsqueue_include_cl3_queue = true
+  ws_queue_scheduler_type = '::adt::WsClv3Queue'
+elif ws_queue_scheduler == 'cl4'
+  lib_adt_wsqueue_include_cl4_queue = true
+  ws_queue_scheduler_type = '::adt::WsClv4Queue'
 else
   error('Unknown setting for ws_queue_scheduler: ' + ws_queue_scheduler)
 endif
@@ -38,6 +52,8 @@ endif
 conf_data.set('EMPER_LIB_ADT_WSQUEUE_INCLUDE_LOCKED_QUEUE', lib_adt_wsqueue_include_locked_queue)
 conf_data.set('EMPER_LIB_ADT_WSQUEUE_INCLUDE_CL_QUEUE', lib_adt_wsqueue_include_cl_queue)
 conf_data.set('EMPER_LIB_ADT_WSQUEUE_INCLUDE_CL2_QUEUE', lib_adt_wsqueue_include_cl2_queue)
+conf_data.set('EMPER_LIB_ADT_WSQUEUE_INCLUDE_CL3_QUEUE', lib_adt_wsqueue_include_cl3_queue)
+conf_data.set('EMPER_LIB_ADT_WSQUEUE_INCLUDE_CL4_QUEUE', lib_adt_wsqueue_include_cl4_queue)
 
 conf_data.set('EMPER_WS_QUEUE_DEFAULT_TYPE', ws_queue_default_type)
 conf_data.set('EMPER_WS_QUEUE_SCHEDULER_TYPE', ws_queue_scheduler_type)
diff --git a/extra-libs/meson.build b/extra-libs/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..72019537126c9c354fa8909b4c0a1abda528c9c6
--- /dev/null
+++ b/extra-libs/meson.build
@@ -0,0 +1 @@
+subdir('ws-queue')
diff --git a/extra-libs/ws-queue/WorkStealingQueueLib.cpp b/extra-libs/ws-queue/WorkStealingQueueLib.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..c39bb20649bf3439d1f56fc1b2f70498b109f4e6
--- /dev/null
+++ b/extra-libs/ws-queue/WorkStealingQueueLib.cpp
@@ -0,0 +1,34 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2022 Florian Schmaus
+#include "WorkStealingQueueLib.hpp"
+
+#include "StealingResult.hpp"
+#include "lib/adt/PushBottomResult.hpp"
+#include "lib/adt/WsClv3Queue.hpp"
+#include "lib/adt/WsClv4Queue.hpp"
+
+namespace emper::extralibs::wsqueue {
+
+constexpr int maxRetries = 0;
+
+namespace cl3 {
+static adt::WsClv3Queue<int, 16> queue;
+
+auto pushBottom(int value) -> adt::PushBottomResult { return queue.pushBottom(value); }
+
+auto popBottom(int* value) -> bool { return queue.popBottom(value); }
+
+auto popTop(int* value) -> StealingResult { return queue.popTop<maxRetries>(value); }
+}	 // namespace cl3
+
+namespace cl4 {
+static adt::WsClv4Queue<int, 16> queue;
+
+auto pushBottom(int value) -> adt::PushBottomResult { return queue.pushBottom(value); }
+
+auto popBottom(int* value) -> bool { return queue.popBottom(value); }
+
+auto popTop(int* value) -> StealingResult { return queue.popTop<maxRetries>(value); }
+}	 // namespace cl4
+
+}	 // namespace emper::extralibs::wsqueue
diff --git a/extra-libs/ws-queue/WorkStealingQueueLib.hpp b/extra-libs/ws-queue/WorkStealingQueueLib.hpp
new file mode 100644
index 0000000000000000000000000000000000000000..8ffe24e0b5e129efcf4a05a859c1ba03b3f439c2
--- /dev/null
+++ b/extra-libs/ws-queue/WorkStealingQueueLib.hpp
@@ -0,0 +1,26 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+// Copyright © 2022 Florian Schmaus
+#pragma once
+
+#include "StealingResult.hpp"
+#include "lib/adt/PushBottomResult.hpp"
+
+namespace emper::extralibs::wsqueue {
+
+namespace cl3 {
+auto pushBottom(int value) -> adt::PushBottomResult;
+
+auto popBottom(int* value) -> bool;
+
+auto popTop(int* value) -> StealingResult;
+}	 // namespace cl3
+
+namespace cl4 {
+auto pushBottom(int value) -> adt::PushBottomResult;
+
+auto popBottom(int* value) -> bool;
+
+auto popTop(int* value) -> StealingResult;
+}	 // namespace cl4
+
+}	 // namespace emper::extralibs::wsqueue
diff --git a/extra-libs/ws-queue/meson.build b/extra-libs/ws-queue/meson.build
new file mode 100644
index 0000000000000000000000000000000000000000..3196606c2bdbb4186bc4987aaa82b2b97d5ac1dc
--- /dev/null
+++ b/extra-libs/ws-queue/meson.build
@@ -0,0 +1,5 @@
+ws_queue_lib = library(
+  'ws-queue',
+  ['WorkStealingQueueLib.cpp'],
+  include_directories: emper_all_include,
+)
diff --git a/meson.build b/meson.build
index 6d86de05b83b0b0687abe50cacfb9ce3112690ba..6ef7c078f701a26f69c6b4310a70c7bbe209f3b6 100644
--- a/meson.build
+++ b/meson.build
@@ -231,6 +231,7 @@ if get_option('io_single_uring')
 endif
 
 subdir('emper')
+subdir('extra-libs')
 subdir('tests')
 subdir('apps')
 subdir('eval')
diff --git a/meson_options.txt b/meson_options.txt
index 49ad8d4fcf82166c57190629734499a964059a50..9a5dfb73daee0df4b031e92711eb11de7e890551 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -78,6 +78,8 @@ option(
     'locked',
     'cl',
     'cl2',
+    'cl3',
+    'cl4',
   ],
   value: 'cl',
   description: 'The default work-stealing queue to use in the runtime-system',
@@ -90,6 +92,8 @@ option(
     'locked',
     'cl',
     'cl2',
+    'cl3',
+    'cl4',
   ],
   value: 'default',
   description: 'The work-stealing queue to use for scheduling',