Semaphore is yet another useful thread synchronization mechanism. But unlike mutex, semaphore allows more than one thread to access similar shared resources at the same time. Its synchronization primitive supports two types, that is, binary semaphore and counting semaphore.
Binary semaphore works just like a mutex, that is, only one thread can access the shared resource at any point of time. However, the difference is that a mutex lock can only be released by the same thread that owns it; however, a semaphore lock can be released by any thread. The other notable difference is that generally, a mutex works within the process boundary whereas semaphores are used across the process boundary. This is because it is a heavyweight lock, unlike the mutex. However, a mutex can also be used across the process if created in the shared memory region.
Counting semaphores let multiple threads share a limited number of shared resources. While mutex lets one thread access the shared resource at a time, counting semaphores allow multiple threads to share a limited number of resources, which is generally at least two or more. If a shared resource has to be accessed one thread at a time but the threads are across the process boundary, then a binary semaphore can be used. Though the use of a binary semaphore within the same process is a possibility as a binary semaphore is heavy, it isn't efficient, but it works within the same process as well.
Unfortunately, the C++ thread support library doesn't support semaphores and shared memory natively until C++17. C++17 supports lock-free programming using atomic operations, which must ensure atomic operations are thread-safe. Semaphores and shared memory let threads from other processes modify the shared resources, which is quite challenging for the concurrency module to assure thread safety of atomic operations across the process boundary. C++20 seems to bet big on concurrency, hence we need to wait and watch the move.
However, it doesn't stop you from implementing your own semaphore using the mutex and conditional variable offered by the thread support library. Developing a custom semaphore class that shares common resources within the process boundary is comparatively easy, but semaphores come in two flavors: named and unnamed. Named semaphore is used to synchronize common resources across the boundary, which is tricky.
Alternatively, you could write a wrapper class around the POSIX pthreads semaphore primitive, which supports both named and unnamed semaphores. If you are developing a cross-platform application, writing portable code that works across all platforms is a requirement. If you go down this road, you may end up writing platform-specific code for each platform--yes, I heard it; sounds weird, right?
The Qt application framework supports semaphores natively. The use of the Qt Framework is a good choice as it is cross-platform. The downside is that the Qt Framework is a third-party framework.
The bottom line is you may have to choose between pthreads and the Qt Framework or refactor your design and try to solve things with native C++ features. Restricting your application development using only C++ native features is difficult but guarantees portability across all platforms.