C++, volatile, and the Decorator pattern
Posted: Thu Sep 29, 2005 11:25 pm
I mentioned this article in another thread recently. It describes a way of using volatile and smart pointers to make multithreaded programming safer. It's a pretty cool technique, but there is a design case that doesn't mesh well with it that I'm struggling with.
Let's say you're designing an interface IFoo, but you don't know offhand whether objects that implement IFoo will be thread-safe or not. In the pre-volatile world, this would probably be ok, because you can always use the decorator pattern later to add thread-safety:
The problem occurs when you start throwing volatile qualifiers in there. If I design IFoo so that it has both volatile and non-volatile versions of bar(), suddenly I'm obligated by the contract to make all implementations of IFoo thread-safe. Otherwise, what happens if someone declares a SingleThreadedFoo as volatile? Bad things would happen -- things that the volatile scheme is supposed to prevent.
I suppose in the above example I could throw an exception in the volatile bar(), but then any mis-uses of SingleThreadedFoo won't be caught until run-time.
So what if I keep the design of IFoo as it is? Then the problem is that my thread-safe wrapper instances cannot be declared volatile, which breaks the convention used in the article that all objects shared by multiple threads should be declared volatile.
As far as I can tell, as long as no local state of SynchronizingFoo is itself modified, it should be safe to declare shared instances as non-volatile. However, it really contradicts the intention of the volatile-is-shared scheme. The only alternative I can see is to have two different interfaces -- IFoo and ISynchronizedFoo. However, this is ugly, and troublesome, since what if I have a ton of existing code (for which I have libraries only and no source) that relies on IFoo and therefore doesn't understand ISynchronizedFoo? Ugh!
What would you do?
Alternatively, what do you think Bjarne would do?
Let's say you're designing an interface IFoo, but you don't know offhand whether objects that implement IFoo will be thread-safe or not. In the pre-volatile world, this would probably be ok, because you can always use the decorator pattern later to add thread-safety:
Code: Select all
class IFoo
{
public:
virtual void bar() = 0;
};
class SingleThreadedFoo : public IFoo
{
...
};
class SynchronizingFoo : public IFoo
{
public:
SynchronizingFoo( IFoo* foo ) : m_foo( foo ) {}
void bar()
{
AutoLock lock( m_mutex );
m_foo->bar();
}
private:
std::auto_ptr<IFoo> m_foo;
Mutex m_mutex;
};
Code: Select all
class IFoo
{
public:
virtual void bar() = 0;
virtual void bar() volatile = 0;
};
class SingleThreadedFoo : public IFoo
{
public:
// Thread-unsafe version of bar().
void bar()
{
mutate_somehow( m_localState );
}
void bar() volatile
{
// BAD! We're lying about being thread-safe!
SingleThreadedFoo* nonVolatileThis =
const_cast<SingleThreadedFoo*>( this );
nonVolatileThis->bar();
}
private:
Baz m_localState;
};
...
volatile SingleThreadedFoo foo;
// Uh-oh.
run_thread1( foo );
run_thread2( foo );
So what if I keep the design of IFoo as it is? Then the problem is that my thread-safe wrapper instances cannot be declared volatile, which breaks the convention used in the article that all objects shared by multiple threads should be declared volatile.
Code: Select all
// This is how it should be done...
static volatile SynchronizingFoo foo( new SingleThreadedFoo() );
foo->bar(); // ILLEGAL, won't compile because bar is not volatile!
What would you do?
Alternatively, what do you think Bjarne would do?