// suppose libobj is the class provided by the library
class my_libobj : public libobj {
// c开发者_高级运维ode
};
This only inherits from libobj
, which may or may not "work" depending on whether the class was designed for inheritance (has at least a virtual
destructor).
In any case, it won't buy you thread-safety for free. The easiest way to get that is to add mutexes to the class and lock those when entering a critical section:
class my_obj {
libobj obj; // inheritance *might* work too
boost::mutex mtx;
void critical_op()
{
boost::unique_lock lck(mtx);
obj.critical_op();
}
};
(This is very coarse-grained design with a single mutex; you may to able to make it more fine-grained if you know the behavior of the various operations. It's also not fool-proof, as @dribeas explains.)
Retrofitting thread safety -- and BTW, there are different level -- in a library which hasn't be designed for is probably impossible without knowing how it has be implemented if you aren't content with just serializing all calls to it, and even then it can be problematic if the interface is bad enough -- see strtok for instance.
This is impossible to answer without knowledge of at least the actual interface of the class. In general the answer would be no.
From the practical C++ point of view, if the class was not designed to be extended, every non-virtual method will not be overriden and as such you might end up with a mixture of some thread-safe and some non-thread safe methods.
Even if you decide to wrap (without inheritance) and force delegation only while holding a lock, the approach is still not valid in all cases. Thread safety requires not only locking, but an interface that can be made thread safe.
Consider a stack
implementation as in the STL, by just adding a layer of locking (i.e. making every method thread safe, you will not guarantee thread safety on the container. Consider a few threads adding elements to the stack and two threads pulling information:
if ( !stack.empty() ) { // 1
x = stack.top(); // 2
stack.pop(); // 3
// operate on data
}
There are a number of possible things that can go wrong here: Both threads might perform test [1] when the stack has a single element and then enter sequentially, in which case the second thread will fail in [2] (or obtain the same value and fail in [3]), even in the case where there are multiple objects in the container, both threads could execute [1] and [2] before any of them executing [3], in which case both threads would be consuming the same object, and the second element in the stack would be discarded without processing...
Thread safety requires (in most cases) changes to the API, in the example above, an interface that provides bool pop( T& v );
implemented as:
bool stack::try_pop( T& v ) { // argument by reference, to provide exception safety
std::lock<std:mutex> l(m);
if ( s.empty() ) return false; // someone consumed all data, return failure
v = s.top(); // top and pop are executed "atomically"
s.pop();
return true; // we did consume one datum
}
Of course there are other approaches, you might not return failure but rather wait on a condition in a pop
operation that is guaranteed to lock until a datum is ready by making use of a conditional variable or something alike...
The simplest solution is to create a single thread uniquely for that library, and only access the library from that thread, using message queues to pass requests and return parameters.
精彩评论