diff --git a/SimoxUtility/CMakeLists.txt b/SimoxUtility/CMakeLists.txt
index 02e16fe9dcf44ab1d8452047f998e4aa9c10b89a..2745556a2d74708f200d3b2cc9dd4902a10b178a 100644
--- a/SimoxUtility/CMakeLists.txt
+++ b/SimoxUtility/CMakeLists.txt
@@ -88,6 +88,7 @@ SET(SOURCES
     shapes/json_conversions.cpp
 
     threads/system_thread_id.cpp
+    threads/CountingSemaphore.cpp
 )
 
 SET(INCLUDES
@@ -256,6 +257,7 @@ SET(INCLUDES
     backport/span/gcc_header.h
 
     threads/system_thread_id.h
+    threads/CountingSemaphore.h
 )
 
 simox_generate_subdir_headers(
diff --git a/SimoxUtility/threads.h b/SimoxUtility/threads.h
index cd685bff07eeb0cd366b4c6f4390d714c20494c9..7c9962102a968d6bf46aa18e66aed4895df62c51 100644
--- a/SimoxUtility/threads.h
+++ b/SimoxUtility/threads.h
@@ -2,4 +2,5 @@
 
 // This file is generated!
 
+#include "threads/CountingSemaphore.h"
 #include "threads/system_thread_id.h"
diff --git a/SimoxUtility/threads/CountingSemaphore.cpp b/SimoxUtility/threads/CountingSemaphore.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..9e6687e62c9900e841dc2f6b73dd9fe50b5b8812
--- /dev/null
+++ b/SimoxUtility/threads/CountingSemaphore.cpp
@@ -0,0 +1,42 @@
+#include "CountingSemaphore.h"
+
+
+namespace simox::threads
+{
+
+    CountingSemaphore::CountingSemaphore()
+    {}
+
+    CountingSemaphore::CountingSemaphore(unsigned int count) : _count(count)
+    {}
+
+
+    void CountingSemaphore::notify()
+    {
+        std::lock_guard<std::mutex> lock(_mutex);
+        ++_count;
+        _condition.notify_one();
+    }
+
+    void CountingSemaphore::wait()
+    {
+        std::unique_lock<std::mutex> lock(_mutex);
+        _condition.wait(lock, [this]()
+        {
+            return _count > 0;
+        });
+        --_count;
+    }
+
+    bool CountingSemaphore::try_wait()
+    {
+        std::unique_lock<std::mutex> lock(_mutex);
+        if (_count > 0)
+        {
+            --_count;
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/SimoxUtility/threads/CountingSemaphore.h b/SimoxUtility/threads/CountingSemaphore.h
new file mode 100644
index 0000000000000000000000000000000000000000..a7bc78f9059c7584b0f3cda781ca487e8d2ad726
--- /dev/null
+++ b/SimoxUtility/threads/CountingSemaphore.h
@@ -0,0 +1,61 @@
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+
+
+namespace simox::threads
+{
+
+    /**
+     * @brief A counting semaphore.
+     *
+     * Threads can enter when the internal count is > 0.
+     * Notifiying the semaphore increments the count and allows threads to enter.
+     * A thread can wait until it may enter. When it enters, it decrements
+     * the internal count.
+     *
+     * Can be used e.g. in a Producer-Consumer pattern.
+     * The producer signals new jobs via `notify()`, while the consumer waits
+     * for new jobs via `wait()`.
+     */
+    class CountingSemaphore
+    {
+    public:
+
+        /// Construct an initially blocking semaphore (initial count 0).
+        CountingSemaphore();
+        /// Construct a semaphore with the given count.
+        CountingSemaphore(unsigned int count);
+
+        /**
+         * @brief Signal that one waiting thread may continue.
+         * Also known as `post()` or `signal()`.
+         */
+        void notify();
+
+        /**
+         * @brief Wait until a thread may enter.
+         */
+        void wait();
+
+        /**
+         * @brief Try to enter. If the semaphore is currently blocking, return false.
+         * @return True if entering was successful, false if semaphore was blocking.
+         */
+        bool try_wait();
+
+
+    private:
+
+        /// The mutex for _condition and _count.
+        std::mutex _mutex;
+        /// The condition variable to wake up waking threads.
+        std::condition_variable _condition;
+
+        /// The current count. Waiting threads may enter when > 0.
+        unsigned int _count = 0;
+
+    };
+
+}