1
votes

Suppose I write a C++ semaphore class with an interface that models the boost Lockable concept (i.e. lock(); unlock(); try_lock(); etc.). Is it safe/recommended to use boost locks for RAII access to such an object? In other words, do boost locks (and/or other related parts of the boost thread library) assume that the Lockable concept will only be modeled by mutex-like objects which are locked and unlocked from the same thread?

My guess is that it should be OK to use a semaphore as a model for Lockable. I've browsed through some of the boost source and it "seems" OK. The locks don't appear to store explicit references to this_thread or anything like that. Moreover, the Lockable concept doesn't have any function like whichThreadOwnsMe(). It also looks like I should even be able to pass a boost::unique_lock<MySemaphore> reference to boost::condition_variable_any::wait. However, the documentation is not explicitly clear about the requirements.

To illustrate what I mean, consider a bare-bones binary semaphore class along these lines:

class MySemaphore{
  bool locked;
  boost::mutex mx;
  boost::condition_variable cv;
public:
  void lock(){
    boost::unique_lock<boost::mutex> lck(mx);
    while(locked) cv.wait(lck);
    locked=true;
  }

  void unlock(){
    {
      boost::lock_guard<boost::mutex> lck(mx);
      if(!locked) error();
      locked=false;
    }
    cv.notify_one();
  }
// bool try_lock(); void error(); etc.
}

Now suppose that somewhere, either on an object or globally, I have

MySemaphore sem;

I want to lock and unlock it using RAII. Also I want to be able to "pass" ownership of the lock from one thread to another. For example, in one thread I execute

void doTask()
{
  boost::unique_lock<MySemaphore> lock(sem);
  doSomeWorkWithSharedObject();
  signalToSecondThread();
  waitForSignalAck();
  lock.release();
}

While another thread is executing something like

{
waitForSignalFromFirstThread();
ackSignal();
boost::unique_lock<MySemaphore>(sem,boost::adopt_lock_t());
doMoreWorkWithSameSharedObject();
}

The reason I am doing this is that I don't want anyone else to be able to get the lock on sem in between the time that the first thread executes doSomeWorkWithSharedObject() and the time the second executes doMoreWorkWithSameSharedObject(). Basically, I'm splitting one task into two parts. And the reason I'm splitting the task up is because (1) I want the first part of the task to get started as soon as possible, (2) I want to guarantee that the first part is complete before doTask() returns, and (3) I want the second, more time-consuming part of the task to be completed by another thread, possibly chosen from a pool of slave threads that are waiting around to finish tasks that have been started by master threads.

NOTE: I recently posted this same question (sort of) here Modelling boost::Lockable with semaphore rather than mutex (previously titled: Unlocking a mutex from a different thread) but I confused mutexes with semaphores, and so the question about using boost locks didn't really get addressed.

1
You can change the title of the other question. I think that would be better than reposting. - Potatoswatter
The post-condition for the lock function of Lockable, and the pre-condition for the unlock function, are that this thread "owns" the lock. So although there's no function to return the owning thread, Lockable is defined in those terms. Now, there's nothing to say that a Lockable object can't have another function to transfer ownership of the lock atomically from one thread to another, so I don't think you're violating the contract of Lockable to do this. Actually implementing that atomic transfer of ownership is going to be tricksy, depending how reliable you want it to be. - Steve Jessop
... also, if you use a unique_lock and transfer ownership of your lock during the lifetime of that object, then you're in the same state of sin as if you used a unique_lock and called unlock during the lifetime. You're messing with the contract of unique_lock, which is that it manages ownership of the Lockable, and you leave it to do so. - Steve Jessop
@Steve: It seems to me that since the Lockable can never be asked which thread owns it, then I don't need to actually transfer the ownership, provided I guarantee that each call to lock is followed by exactly one call to unlock (from any thread). Or, put a different way, whenever any thread calls unlock, I could implicitly transfer ownership to the unlocking thread, without actually doing anything. It seems to me that this behavior is indistinguishable from physically transferring ownership. - dan
@steve: The gist of this question is whether that "current thread" reference is meaningful. A semaphore is not owned by a particular thread. It typically models passing an invisible token (or tokens) around. Traditionally those tokens are not modeled by objects at all, so it's quite unclear that Dan needs to implement transfer of anything. - Potatoswatter

1 Answers

2
votes

@dan, I think you are overcomplicating things. What you are describing is easily achievable with a main processing thread, a synchronized queue, and a [pool of] worker thread(s). It also looks like you are falling into a common trap of using locks to "protect code", while it's the data structures you need to protect.

Define your shared data, identify minimal critical sections when the data might be inconsistent. Brace that with locks.