diff --git a/.editorconfig b/.editorconfig
index 8cfde3d91e659c9f110ae5ff52626185e5b51c38..5af1ee3129d262509b76e728eb3c3ff1bc977176 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -10,3 +10,11 @@ insert_final_newline = true
 [*.{c,h,cpp,hpp}]
 indent_style = tab
 indent_size = 2
+
+[*.yml]
+indent_style = space
+indent_size = 2
+
+[meson_options.txt]
+indent_style = space
+indent_size = 2
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 94f24b0c47c6175a962c59c273d2e2201c40d95d..9ffdfe9964ec3afd789fa5e0f63b0938b06adb53 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -56,6 +56,12 @@ fast-static-analysis:
    variables:
        EMPER_IO: "true"
 
+.fast-variant-check:
+   stage: test
+   script: make fast-static-analysis smoke-test-suite
+   variables:
+       EMPER_IO: "true"
+
 iwyu:
    stage: smoke-test
    script: IWYU_TOOL="${CI_PROJECT_DIR}/tools/iwyu_tool.py" make iwyu
@@ -361,3 +367,27 @@ test-io-stealing-pipe-no-completer-lockless:
     - .emper-no-completer
     - .emper-io-stealing
     - .emper-lockless-cq
+
+smoke-test-locked-queue-rwlock:
+  extends:
+    - .fast-variant-check
+  variables:
+    EMPER_LOCKED_UNBOUNDED_QUEUE_IMPLEMENTATION: "rwlock"
+
+smoke-test-locked-queue-shared-mutex:
+  extends:
+    - .fast-variant-check
+  variables:
+    EMPER_LOCKED_UNBOUNDED_QUEUE_IMPLEMENTATION: "shared_mutex"
+
+smoke-test-locked-queue-boost-shared-mutex:
+  extends:
+    - .fast-variant-check
+  variables:
+    EMPER_LOCKED_UNBOUNDED_QUEUE_IMPLEMENTATION: "boost_shared_mutex"
+
+smoke-test-locked-queue-boost-userspace-rcu:
+  extends:
+    - .fast-variant-check
+  variables:
+    EMPER_USERSPACE_RCU: "true"
diff --git a/emper/lib/adt/RwLockUnboundedQueue.hpp b/emper/lib/adt/RwLockUnboundedQueue.hpp
index b52b938e0270a57332f670f9576ef57e43482e92..5d091c9da577843fbff4ddad18feac05bf80fa25 100644
--- a/emper/lib/adt/RwLockUnboundedQueue.hpp
+++ b/emper/lib/adt/RwLockUnboundedQueue.hpp
@@ -11,13 +11,6 @@
 
 namespace lib::adt {
 
-static void aquire_wrlock(pthread_rwlock_t& lock) {
-	int err = pthread_rwlock_wrlock(&lock);
-	if (unlikely(err)) {
-		DIE_MSG("pthread_rwlock_wrlock failed: " << strerror(err));
-	}
-}
-
 template <typename I>
 class RwLockUnboundedQueue {
  private:
@@ -25,6 +18,27 @@ class RwLockUnboundedQueue {
 
 	std::queue<I*> queue;
 
+	void acquireWriteLock() {
+		int err = pthread_rwlock_wrlock(&lock);
+		if (unlikely(err)) {
+			DIE_MSG("pthread_rwlock_wrlock failed: " << strerror(err));
+		}
+	}
+
+	void acquireReadLock() {
+		int err = pthread_rwlock_rdlock(&lock);
+		if (unlikely(err)) {
+			DIE_MSG("pthread_rwlock_rdlock failed: " << strerror(err));
+		}
+	}
+
+	void unlock() { pthread_rwlock_unlock(&lock); }
+
+	void upgradeLock() {
+		unlock();
+		acquireWriteLock();
+	}
+
  public:
 	RwLockUnboundedQueue() {
 		int err = pthread_rwlock_init(&lock, nullptr);
@@ -41,89 +55,60 @@ class RwLockUnboundedQueue {
 	}
 
 	void enqueue(I* item) {
-		aquire_wrlock(lock);
+		acquireWriteLock();
 		queue.push(item);
-		pthread_rwlock_unlock(&lock);
+		unlock();
 	}
 
 	template <class InputIt>
 	void insert(InputIt begin, InputIt end) {
-		aquire_wrlock(lock);
+		acquireWriteLock();
 		for (; begin != end; ++begin) {
 			queue.push(*begin);
 		}
-		pthread_rwlock_unlock(&lock);
+		unlock();
 	}
 
 	void insert(I** items, unsigned count) {
-		aquire_wrlock(lock);
+		acquireWriteLock();
 		for (unsigned i = 0; i < count; ++i) {
 			queue.push(items[i]);
 		}
-		pthread_rwlock_unlock(&lock);
+		unlock();
 	}
 
 	auto dequeue() -> I* {
 		I* res = nullptr;
 
-		int err = pthread_rwlock_rdlock(&lock);
-		if (unlikely(err)) {
-			DIE_MSG("pthread_rwlock_rdlock failed: " << strerror(err));
-		}
-
+		acquireReadLock();
 		if (queue.empty()) {
-			goto unlock_and_return;
+			goto done;
 		}
 
-		// try to upgrade to wrlock
-		err = pthread_rwlock_trywrlock(&lock);
-		if (err) {
-			if (unlikely(err != EBUSY)) {
-				DIE_MSG("pthread_rwlock_trylock failed: " << strerror(err));
-			}
-
-			// drop the read lock and aquire a write lock
-			aquire_wrlock(lock);
-
-			if (queue.empty()) {
-				goto unlock_and_return;
-			}
+		upgradeLock();
+		if (queue.empty()) {
+			goto done;
 		}
 
-		// we certainly hold the wrlock here
 		res = queue.front();
 		queue.pop();
 
-	unlock_and_return:
-		pthread_rwlock_unlock(&lock);
+	done:
+		unlock();
 		return res;
 	}
 
 	template <class OutputIt>
 	auto dequeue(OutputIt begin, OutputIt end) -> size_t {
-		int err = pthread_rwlock_rdlock(&lock);
-		if (unlikely(err)) {
-			DIE_MSG("pthread_rwlock_rdlock failed: " << strerror(err));
-		}
-
+		acquireReadLock();
 		OutputIt cur = begin;
 		if (queue.empty()) {
-			goto unlock;
+			goto done;
 		}
 
-		// try to upgrade to wrlock
-		err = pthread_rwlock_trywrlock(&lock);
-		if (err) {
-			if (unlikely(err != EBUSY)) {
-				DIE_MSG("pthread_rwlock_trylock failed: " << strerror(err));
-			}
-
-			// drop the read lock and aquire a write lock
-			aquire_wrlock(lock);
-
-			if (queue.empty()) {
-				goto unlock;
-			}
+		upgradeLock();
+		if (queue.empty()) {
+			goto done;
 		}
 
 		for (; !queue.empty() && cur != end; ++cur) {
@@ -131,8 +116,8 @@ class RwLockUnboundedQueue {
 			queue.pop();
 		}
 
-	unlock:
-		pthread_rwlock_unlock(&lock);
+	done:
+		unlock();
 		return cur - begin;
 	}
 };
diff --git a/emper/lib/adt/SharedMutexUnboundedQueue.hpp b/emper/lib/adt/SharedMutexUnboundedQueue.hpp
index 5f98ff0c60caf780f80621b1060847186f1d440e..089eb276dfd35aaf7d605f6611a6000db6915c01 100644
--- a/emper/lib/adt/SharedMutexUnboundedQueue.hpp
+++ b/emper/lib/adt/SharedMutexUnboundedQueue.hpp
@@ -2,6 +2,7 @@
 // Copyright © 2020-2021 Florian Schmaus, Florian Fischer
 #pragma once
 
+#include <mutex>
 #include <queue>
 #include <shared_mutex>