1

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.

Community
  • 1
  • 1
dan
  • 151
  • 2
  • 7
  • 1
    You can change the title of the other question. I think that would be better than reposting. – Potatoswatter May 03 '10 at 01:24
  • 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 May 03 '10 at 01:30
  • ... 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 May 03 '10 at 01:35
  • @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 May 03 '10 at 01:39
  • @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 May 03 '10 at 01:41
  • @Steve: `unique_lock` provides the `release` function which breaks the relationship between the `unique_lock` object and the Lockable, which can be used, as in the example code above, to transfer ownership. Such a transfer would be uncontroversial except for the cross-thread aspect; in other words the `unique_lock` contract is not necessarily a "lifetime contract"; it can be cancelled. – dan May 03 '10 at 01:48
  • Also, note that `unique_lock` supports `swap` and in C++0x is `movable`, so it does support seamlessly transferring itself to another owner. – Potatoswatter May 03 '10 at 01:50
  • possible duplicate of http://stackoverflow.com/questions/2754884/unlocking-a-mutex-from-a-different-thread-c – Potatoswatter May 03 '10 at 01:53
  • @Potato: Sorry about the dup, I'm new here, don't know the etiquette. If I could somehow usefully merge the questions or whatever I would; I figured this is a more accurate version of the original question, which was really not about mutexes versus semaphores but rather about the boost Lockable concept. – dan May 03 '10 at 01:58
  • I don't think there's really any new development here. If you see a delete button under the question, try clicking it. If you modify the other question, people will take it at its new face value. Old questions don't really slip through the cracks on this site. (I apologize for all the furor though, rather surprising that a semaphores vs mutexes war would flare up.) – Potatoswatter May 03 '10 at 02:04
  • "The gist of this question is whether that "current thread" reference is meaningful" - it's the fundamental basis of the Lockable abstraction, so it had better be :-). It's like "containment" for the Container concept, or "strict weak order" for comparators. "ownership" doesn't have to be represented by a physical object which is "actually passed", but the concept must be there. You're right, if you can atomically "pass conceptual ownership to another thread and call release on the original unique_lock", then you're sorted. Doesn't matter whether "ownership" is represented by an "owner" field. – Steve Jessop May 03 '10 at 12:15
  • Come to think of it, you don't have to atomically call release, you just have to not throw an exception between transferring ownership and calling release. If some other thread provokes the transfer by calling "unlock", as in your hypothetical example, then calling "release" on the unique_lock would be hard-to-impossible. But I don't think you need that, you just need for the thread that owns the lock to give up ownership to a specific other thread. – Steve Jessop May 03 '10 at 12:18

1 Answers1

2

@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.

Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171