views:

153

answers:

2

I noticed that boost does not seem to support semaphores. What's the easiest way to achieve a similar effect?

+4  A: 

You either need Boost Interprocess semaphore or Boost Thread synchronization primitives.

Mutex/Lock and condition are primitives that are commonly used to synchronize access to shared resources across multiple threads of a single process. There are exclusive, readers-writer and recursive/reentrant types of mutexes. Mutex, in other words, is an exclusive lock. Condition is used to achieve atomicity when you need to unlock the mutex and wait for object to change. When you start waiting on a condition, it unlocks the mutex and guarantees than unlock + call to wait is atomic and no other threads can modify a resource between those two operations.

Semaphore, on another case, is a mix of condition and mutex, and is used for exactly the same purpose but to synchronize access across processes.

See Mutex vs Semaphore.

There is also such thing as non-blocking/lock-free synchronization that is becoming very popular these days. I personally use it in high-frequency trading applications when amount of data is relatively very large and low latency does matter a lot.

In your case, I assume 5 philosophers can have a dinner inside a single process with 5 threads. In that case you have to use a mutex, not a semaphore. You might or might not use condition though. It depends on what exactly and how exactly you want to implement that dining procedure.

I am not sure how to describe it better as I will end up writing a book about it. So I'd recommend you find some book that is already written to understand the basic concepts. Once you know basics, you can use APIs/libraries/frameworks like POSIX threads, Boost Interprocess or Thread, ACE or even non-blocking algorithms to achieve what you want.

Good luck!

Vlad Lazarenko
So, just out of curiosity, the name "interprocess semaphore" suggests it is meant to be shared between processes rather than threads. Does this imply that it costs additional overhead above what a introprocess semaphore would theoretically use? I'm not sure how to easily use thread conditions for applications such as the toy application mentioned in my comment on the question.
jonderry
@jonderry: OK, I thought I can get away with a simple answer, but you got me. I've started replying in a comment but it was too large with lots of links so I ended up editing my answer. Please see updated version. Thanks.
Vlad Lazarenko
Thanks, Vlad. It is true that the dining philosophers problem uses mutexes on the adjacent forks, but if you don't add something more, you get deadlock. One standard way to solve this is to only allow 4 philosophers to dine at once so one can always make progress. The natural way to achieve this is with a semaphore.
jonderry
@jonderry: Semaphore is an equivalent of mutex + condition inside the process. Look at the code in Doug's answer, he've got an idea. I'd recommend you read some good book on thread.
Vlad Lazarenko
+2  A: 

This is one way of implementing a very simple semaphore using Boost.Thread. It's an inter-thread semaphore, not an interprocess one. No warranties implied, etc. - I haven't even compiled the code. It illustrates how mutexes and condition variables interact, and assumes a reasonably recent version of Boost.

Notice how the mutex and condition variable are "paired" - threads must have a lock to the mutex to wait on the condition variable, and re-acquire the lock when they're woken up. Also, the code that changes the data needs to explicitly wake up other code that might be waiting. This means that the mutex, condition variable, data, and the condition(s) that cause the wakeup, are all closely coupled. The tight coupling also means that the data, mutex, and condition variable should be encapsulated if possible - any external modification can break the code in strange ways, including deadlocks, missed wakeups, and other strange bugs.

All this is really meant as a complement to Vlad Lazarenko's answer - understanding the theory and principles are at least as important as having "working" code, in multi-threaded programming.

#include <boost/thread/condition_variable.hpp>
#include <boost/thread/mutex.hpp>    
#include <boost/thread/lock_types.hpp>


class semaphore
{
    //The current semaphore count.
    unsigned int count_;

    //mutex_ protects count_.
    //Any code that reads or writes the count_ data must hold a lock on
    //the mutex.
    boost::mutex mutex_;

    //Code that increments count_ must notify the condition variable.
    boost::condition_variable condition_;

public:
    explicit semaphore(unsigned int initial_count) 
       : count_(initial_count),
         mutex_(), 
         condition_()
    {
    }

    unsigned int get_count() const //for debugging/testing only
    {
        //The "lock" object locks the mutex when it's constructed,
        //and unlocks it when it's destroyed.
        boost::unique_lock<boost::mutex> lock(mutex_);
        return count_;
    }

    void signal() //called "release" in Java
    {
        boost::unique_lock<boost::mutex> lock(mutex_);

        ++count_;

        //Wake up any waiting threads. 
        //Always do this, even if count_ wasn't 0 on entry. 
        //Otherwise, we might not wake up enough waiting threads if we 
        //get a number of signal() calls in a row.
        condition_.notify_one(); 
    }

    void wait() //called "acquire" in Java
    {
        boost::unique_lock<boost::mutex> lock(mutex_);
        while (count_ == 0)
        {
             condition_.wait(lock);
        }
        --count_;
    }

};
Doug