 |
C++Talk.NET C++ language newsgroups
|
| View previous topic :: View next topic |
| Author |
Message |
Bernhard Jungk Guest
|
Posted: Mon Aug 08, 2005 7:51 am Post subject: Thread-safe Dynamic C++ Classes |
|
|
Hi all,
I'm developing a runtime code update strategy. My idea is based on
the "Dynamic C++ Classes" paper, which can be found here:
http://tinyurl.com/7f5g7
The basic idea is to create a proxy object with a smart pointer like
interface and some methods to update the class. (My proxy class is differnt
to the one presented in the paper, but I got the basic idea from there.)
Please forgive me, if the examples are not complete or compilable, I've just
extracted the most important parts, so you can get an idea what I'm doing.
class TestClass
{
public:
void someMethod()
{
std::cout << "someMethod()" << std::endl;
}
};
// T should be a pure virtual base class of both the old and the new classes
template
class Proxy
{
public:
Proxy(T* p)
: p_(p)
{
}
~Proxy()
{
delete p_;
}
T* operator->()
{
return p_;
}
template<class Y>
void update(Y* p)
{
// update p_
}
private:
T* p_;
}
int main()
{
Proxy<TestClass> testObject(new TestClass());
testObject->someMethod();
}
This works in a single threaded environment. In a multithreaded
world, the operator->() and the update() member will concurrently access
the proxy object, resulting in a race condition while updating the object.
My first attempt to serialize access was to introduce a mutex in
operator->() and update():
#include <boost/thread/mutex.hpp>
template<class T>
class Proxy
{
public:
Proxy(T* p)
: p_(p)
{
}
~Proxy()
{
delete p_;
}
T* operator->()
{
boost::mutex::scoped_lock lock(proxyMutex_);
return p_;
}
template<class Y>
void update(Y* p)
{
boost::mutex::scoped_lock lock(proxyMutex_);
// update p_
}
private:
boost::mutex proxyMutex_;
T* p;
}
This fails, because the scoped_lock object will be destroyed before the
member functions gets called. The conclusion is, that this will do nothing
to prevent the race condition.
My second attempt was to introduce a temporary class this way:
template<class T>
class Accessor
{
public:
Accessor(boost::mutex& proxyMutex, T* p)
: p_(p)
{
proxyLock_ = new boost::mutex::scoped_lock(proxyMutex_);
}
~Accessor()
{
delete proxyLock_;
}
T* operator->()
{
return p_;
}
private:
T* p_;
boost::mutex::scoped_lock* proxyLock_;
};
template<class T>
class Proxy
{
public:
Proxy(T* p)
: p_(p)
{
}
~Proxy()
{
delete p_;
}
Accessor<T> operator->() const
{
return Accessor<T>(proxyMutex_, p_);
}
template<class Y>
void update(Y* p)
{
boost::mutex::scoped_lock lock(proxyMutex_);
// update p_
}
private:
T* p_;
boost::mutex proxyMutex_;
};
At least with g++ this results in the correct order, which is mutex lock,
method call and finally mutex unlock. I'm wondering, if the behavior is
guaranteed, that the temporary object returned from Proxy<>::operator->()
gets destructed right after the method call or if this happens only by
accident.
If i'm correct the order of construction/destruction and the method calls is
something like this:
main()
{
Proxy<TestClass> testObject(new TestClass());
--> construct TestClass object on the heap
--> construct Proxy object on the stack
testObject->someMethod();
--> call Proxy<T>::operator->()
--> construct Accessor<T> object on the stack (locks the mutex)
--> return Accessort<T> object
--> return TestClass object (p_)
--> call p_->someMethod()
--> destruct Accessor<T> (unlocks the mutex)
} --> destruct Proxy object which deletes p_
Any corrections or better ideas to do achieve the desired result?
(I will change the mutex to a read/write mutex as soon as the basic idea
works, to allow concurrent non-updating access to the object.)
Greetings,
Bernhard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Mon Aug 08, 2005 10:27 am Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
Bernhard Jungk wrote:
| Quote: | Hi all,
I'm developing a runtime code update strategy. My idea is based on
the "Dynamic C++ Classes" paper, which can be found here:
http://tinyurl.com/7f5g7
|
[]
| Quote: | This works in a single threaded environment. In a multithreaded
world, the operator->() and the update() member will concurrently access
the proxy object, resulting in a race condition while updating the object.
My first attempt to serialize access was to introduce a mutex in
operator->() and update():
|
[]
| Quote: | My second attempt was to introduce a temporary class this way:
template<class T
class Accessor
{
public:
Accessor(boost::mutex& proxyMutex, T* p)
: p_(p)
{
proxyLock_ = new boost::mutex::scoped_lock(proxyMutex_);
}
~Accessor()
{
delete proxyLock_;
}
T* operator->()
{
return p_;
}
private:
T* p_;
boost::mutex::scoped_lock* proxyLock_;
};
template<class T
class Proxy
{
public:
Proxy(T* p)
: p_(p)
{
}
~Proxy()
{
delete p_;
}
Accessor() const
{
return Accessor<T>(proxyMutex_, p_);
}
template<class Y
void update(Y* p)
{
boost::mutex::scoped_lock lock(proxyMutex_);
// update p_
}
private:
T* p_;
boost::mutex proxyMutex_;
};
|
The accessor returned by operator-> may well be copied, so you'll end
up deleting proxyLock_ twice. It seems better to me to lock the mutex
in Accessor::operator->() after the possibe object copy. This way you
also avoid allocating the lock.
template<class T>
class Accessor
{
public:
Accessor(boost::mutex& m, T* p)
: p_(p), mtx_(&m), lock_(m, false)
{
// mtx_ is unlocked here
}
Accessor(Accessor const& other)
: p_(other.p_), mtx_(other.mtx_), lock_(other.mtx_, false)
{
// mtx_ is unlocked here
}
// copy constructable, but non assignable
// operator= won't be generated, because some members are const
// locking in operator->
// unlocking in the compiler generated ~Accessor()
T* operator->()
{
lock_.lock();
return p_;
}
private:
T* const p_;
boost::mutex* const mtx_;
boost::mutex::scoped_lock lock_;
};
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
axter Guest
|
Posted: Mon Aug 08, 2005 3:07 pm Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
Bernhard Jungk wrote:
| Quote: | Hi all,
I'm developing a runtime code update strategy. My idea is based on
the "Dynamic C++ Classes" paper, which can be found here:
http://tinyurl.com/7f5g7
The basic idea is to create a proxy object with a smart pointer like
interface and some methods to update the class. (My proxy class is differnt
to the one presented in the paper, but I got the basic idea from there.)
Please forgive me, if the examples are not complete or compilable, I've just
extracted the most important parts, so you can get an idea what I'm doing.
..............
..............
My second attempt was to introduce a temporary class this way:
template<class T
class Accessor
{
public:
Accessor(boost::mutex& proxyMutex, T* p)
: p_(p)
{
proxyLock_ = new boost::mutex::scoped_lock(proxyMutex_);
}
~Accessor()
{
delete proxyLock_;
}
T* operator->()
{
return p_;
}
private:
T* p_;
boost::mutex::scoped_lock* proxyLock_;
};
template<class T
class Proxy
{
public:
Proxy(T* p)
: p_(p)
{
}
~Proxy()
{
delete p_;
}
Accessor() const
{
return Accessor<T>(proxyMutex_, p_);
}
template<class Y
void update(Y* p)
{
boost::mutex::scoped_lock lock(proxyMutex_);
// update p_
}
private:
T* p_;
boost::mutex proxyMutex_;
};
At least with g++ this results in the correct order, which is mutex lock,
method call and finally mutex unlock. I'm wondering, if the behavior is
guaranteed, that the temporary object returned from Proxy<>::operator->()
gets destructed right after the method call or if this happens only by
accident.
If i'm correct the order of construction/destruction and the method calls is
something like this:
main()
{
Proxy<TestClass> testObject(new TestClass());
--> construct TestClass object on the heap
--> construct Proxy object on the stack
testObject->someMethod();
--> call Proxy<T>::operator->()
--> construct Accessor<T> object on the stack (locks the mutex)
--> return Accessort<T> object
--> return TestClass object (p_)
--> call p_->someMethod()
--> destruct Accessor<T> (unlocks the mutex)
} --> destruct Proxy object which deletes p_
Any corrections or better ideas to do achieve the desired result?
(I will change the mutex to a read/write mutex as soon as the basic idea
works, to allow concurrent non-updating access to the object.)
|
You're forgetting that you're returning by value. That means that the
object is going to get locked and unlocked at least one more time.
This can be a problem if you don't have a recursive lock.
I've created a similar class.
See following:
http://code.axter.com/sync_ptr.h
http://code.axter.com/sync_ctrl.h
What I did to avoid the recursive lock issue, is to have my proxy
object (RefLockPtr) have a reference counter, and have my copy
constructor not lock the object.
The reference counter makes sure that when the count hits zero, that's
when it unlocks.
Using this method, if you pass back by value, you should only lock and
unlock once, instead of twice.
FYI:
I never did get my sync_ptr to work with the boost library locks.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Bernhard Jungk Guest
|
Posted: Tue Aug 09, 2005 8:16 am Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
| Quote: | The accessor returned by operator-> may well be copied, so you'll end
up deleting proxyLock_ twice. It seems better to me to lock the mutex
in Accessor::operator->() after the possibe object copy. This way you
also avoid allocating the lock.
|
Thanks for pointing this out.
What bothers me now is the usage of the Proxy class, I use boost::shared_ptr
in my code as smart pointer. I want to use the proxy transparently with
boost::shared_ptr, the "natural" way:
boost::shared_ptr<T> somePointer = new T();
somePointer->someMemberFunction();
The current design of Proxy (and boost::shared_ptr) requires instead:
boost::shared_ptr<Proxy somePointer = new Proxy<T>(new T());
(*somePointer)->someMemberFunction();
I guess I could specialize boost::shared_ptr for T, but getting this right
is hard and I have to do this over and over again for every type I want to
use with that proxy. I ran out of ideas how to do this differently and more
elegant.
Greetings,
Bernhard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Tue Aug 09, 2005 1:42 pm Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
Bernhard Jungk wrote:
[]
| Quote: | What bothers me now is the usage of the Proxy class, I use boost::shared_ptr
in my code as smart pointer. I want to use the proxy transparently with
boost::shared_ptr, the "natural" way:
boost::shared_ptr<T> somePointer = new T();
somePointer->someMemberFunction();
The current design of Proxy (and boost::shared_ptr) requires instead:
boost::shared_ptr<Proxy somePointer = new Proxy<T>(new T());
(*somePointer)->someMemberFunction();
|
Your proxy overloads operator->, so you should be able to call it
simply like:
somePointer->someMemberFunction();
Which must evaluate as:
somePointer.operator->().operator->().operator()->someMemberFunction();
Where the first operator-> is shared_ptr's one, the second is Proxy's
and the third is Accessor's.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Bernhard Jungk Guest
|
Posted: Tue Aug 09, 2005 3:41 pm Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
| Quote: | Your proxy overloads operator->, so you should be able to call it
simply like:
somePointer->someMemberFunction();
Which must evaluate as:
somePointer.operator->().operator->().operator()->someMemberFunction();
Where the first operator-> is shared_ptr's one, the second is Proxy's
and the third is Accessor's.
|
boost::shared_ptr's operator->() returns a pointer to the Proxy. The
Proxy's operator->() will never be called. Therefore just writing
somePointer->someMemberFunction() is not possible.
I can not use the Proxy directly, because I have an object registry
which stores shared_ptrS and I want to use the Proxy transparently with
that registry.
Greetings,
Bernhard
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
simont Guest
|
Posted: Tue Aug 09, 2005 7:13 pm Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
Maxim Yegorushkin wrote:
| Quote: | Bernhard Jungk wrote:
[]
What bothers me now is the usage of the Proxy class, I use boost::shared_ptr
in my code as smart pointer. I want to use the proxy transparently with
boost::shared_ptr, the "natural" way:
boost::shared_ptr<T> somePointer = new T();
somePointer->someMemberFunction();
The current design of Proxy (and boost::shared_ptr) requires instead:
|
Consider changing Proxy<T> to hold a boost::shared_ptr<T> instead of a
raw pointer. Then just use Proxy<T> directly as you would have used
boost::shared_ptr<Proxy.
| Quote: | boost::shared_ptr<Proxy somePointer = new Proxy<T>(new T());
|
So, the line above becomes:
Proxy<T> someProxy(new T());
| Quote: | (*somePointer)->someMemberFunction();
Your proxy overloads operator->, so you should be able to call it
simply like:
somePointer->someMemberFunction();
Which must evaluate as:
somePointer.operator->().operator->().operator()->someMemberFunction();
Where the first operator-> is shared_ptr's one, the second is Proxy's
and the third is Accessor's.
|
And the same works with the shared_ptr inside the Proxy (just the first
two operators change order).
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
Maxim Yegorushkin Guest
|
Posted: Tue Aug 09, 2005 7:19 pm Post subject: Re: Thread-safe Dynamic C++ Classes |
|
|
Bernhard Jungk wrote:
| Quote: | Your proxy overloads operator->, so you should be able to call it
simply like:
somePointer->someMemberFunction();
Which must evaluate as:
somePointer.operator->().operator->().operator()->someMemberFunction();
Where the first operator-> is shared_ptr's one, the second is Proxy's
and the third is Accessor's.
boost::shared_ptr's operator->() returns a pointer to the Proxy. The
Proxy's operator->() will never be called. Therefore just writing
somePointer->someMemberFunction() is not possible.
|
Sorry, my mistake.
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]
|
|
| Back to top |
|
 |
|
|
You cannot post new topics in this forum You cannot reply to topics in this forum You cannot edit your posts in this forum You cannot delete your posts in this forum You cannot vote in polls in this forum
|
|