views:

95

answers:

2

Assume I have a thread-safe collection of Things (call it a ThingList), and I want to add the following function.

Thing * ThingList::findByName(string name)
{
  return &item[name]; // or something similar..
}

But by doing this, I've delegated the responsibility for thread safety to the calling code, which would have to do something like this:

try 
{
  list.lock(); // NEEDED FOR THREAD SAFETY
  Thing *foo = list.findByName("wibble");
  foo->Bar = 123;
  list.unlock();  
}
catch (...) 
{
  list.unlock();
  throw;
} 

Obviously a RAII lock/unlock object would simplify/remove the try/catch/unlocks, but it's still easy for the caller to forget.

There are a few alternatives I've looked at:

  • Return Thing by value, instead of a pointer - fine unless you need to modify the Thing
  • Add function ThingList::setItemBar(string name, int value) - fine, but these tend to proliferate
  • Return a pointerlike object which locks the list on creation and unlocks it again on destruction. Not sure if this is good/bad practice...

What's the right approach to dealing with this?

+3  A: 

There is no one "right approach"; it depends on the needs of your application.

If at all possible, return things by value, or return a copy that the caller can do whatever they want with.

A variant of the above is to return a modifiable copy, and then provide a way to atomically merge a modified object back into the list. Something like:

Thing t = myThingList.getThing(key);
t.setFoo(f);
t.setBar(b);
myThingList.merge(t);     // ThingList atomically updates the appropriate element

However, that can cause problems if multiple threads try to update the same object.

The "pointerlike object" idea sounds cool, but I suspect it would lead to hard-to-find bugs when some lock isn't getting released somewhere.

I'd try to keep all the locking/unlocking code within ThingList, so the ThingList::set... functions are probably what I would do.

Kristopher Johnson
The "merge" is nice, but there's a hidden assumption that Thing objects know how to locate themselves in the list. You could pass "key" to merge as well, but then you have the issue of merging an item that's been deleted...
Roddy
+2  A: 

store and return boost::shared_ptr s

you have to lock during access but you are safe after the unlock

pm100
boost::shared_ptr does nothing to protect the thread-safety of the collection.
Kristopher Johnson
it depends exactly what thread safety issue you are trying to deal with. YOu have to lock when accessing the collection (read or write) but you have to do that anyway. By returning a shared ptr you at least guarantee that if the collection is changed behind you you are still OK (assuming that the collection is a collection of shared_ptrs too). THis is the model I use all the time in a heavily multi threaded server
pm100
I didn't know shared_ptr was thread safe, but thinking about it there's no reason it couldn't be. I know that Microsoft's COM uses InterlockedIncrement and InterlockedDecrement to do a very light-weight thread safe implementation.
Mark Ransom
@pm100 - a: "You have to lock when accessing the collection (read or write) but you have to do that anyway" - That is exactly what I'm trying to avoid. b: "assuming that the collection is a collection of shared_ptrs". It isn't - it's a collection of Thing objects. c: boost::shared_ptr isn't any more thread-safe than an ordinary pointer - see http://www.boost.org/doc/libs/1_42_0/libs/smart_ptr/shared_ptr.htm#ThreadSafety
Roddy