[thread] Moving a `recursive_lock`

Hello all, I have been working on a project that uses the Boost Thread library, and I realized that there is a part of the code that needs to transfer a lock to a thread that has just been started. My idea for implementing this was to use a `boost::recursive_mutex` and pass a non-`const` reference to a `boost::recursive_mutex::scoped_lock` to the thread via `boost::ref`. Also, to ensure that the `scoped_lock` does not go out of scope until until the lock is moved, I thought to use a `boost::barrier`. After writing some test code, I find it interesting that if I use a `recursive_mutex`, then an assertion fails, but if I use a `boost::mutex`, then the code seems to work fine. Here is the test code that I have been experimenting with (the `recursive_mutex` version, which is also attached): #include <iostream> #include <boost/ref.hpp> #include <boost/thread.hpp> static boost::recursive_mutex s_mutex; static boost::barrier s_lockMoveBarrier(2); void thread_fun1(boost::recursive_mutex::scoped_lock& lock_) { //boost::recursive_mutex::scoped_lock testLock(s_mutex); // uncommenting this causes deadlock, as expected std::cout << "in `void thread_fun1(...)`: moving lock..."; boost::recursive_mutex::scoped_lock lock2(boost::move(lock_)); std::cout << "done" << std::endl; s_lockMoveBarrier.wait(); std::cout << "in `void thread_fun1(...)`: after the barrier" << std::endl; } int main() { boost::recursive_mutex::scoped_lock lock(s_mutex); std::cout << "in `int main()`: lock on `s_mutex` acquired" << std::endl; boost::thread(&thread_fun1, boost::ref(lock)); s_lockMoveBarrier.wait(); std::cout << "in `int main()`: after the barrier" << std::endl; } After compiling and running this on Debian Squeeze with Boost 1.42.0, I get: in `int main()`: lock on `s_mutex` acquired in `void thread_fun1(...)`: moving lock...done in `int main()`: after the barrier a.out: /usr/local/include/boost/thread/pthread/recursive_mutex.hpp:62: boost::recursive_mutex::~recursive_mutex(): Assertion `!pthread_mutex_destroy(&m)' failed. Aborted After replacing "boost::recursive_mutex" with "boost::mutex", however, the output is: in `int main()`: lock on `s_mutex` acquired in `void thread_fun1(...)`: moving lock...done in `void thread_fun1(...)`: after the barrier in `int main()`: after the barrier This leads me to wondering: 1. Is it correct and/or safe to move a lock to a new thread in this way? 2. Why does the `recursive_mutex` version fail and the `mutex` version succeed? Daniel Trebbien

On Sat, 2010-02-13 at 18:50 -0800, Daniel Trebbien wrote:
I have been working on a project that uses the Boost Thread library, and I realized that there is a part of the code that needs to transfer a lock to a thread that has just been started. My idea for implementing this was to use a `boost::recursive_mutex` and pass a non-`const` reference to a `boost::recursive_mutex::scoped_lock` to the thread via `boost::ref`. Also, to ensure that the `scoped_lock` does not go out of scope until until the lock is moved, I thought to use a `boost::barrier`.
Would a boost::shared_ptr<boost::mutex> work in this situation? If you used a shared_ptr you wouldn't have to worry about when stuff goes out of scope. You could also use a thread join to wait for the thread to die instead of a barrier. I'm thinking of something like this. In my view this approach is more strait forward. However, I don't know if it meets your requirements. #include <boost/thread.hpp> #include <iostream> void f(boost::shared_ptr<boost::mutex> M) { boost::mutex::scoped_lock lock(*M); std::cout << "do stuff\n"; } int main() { boost::shared_ptr<boost::mutex> M(new boost::mutex()); boost::thread T; {//BEGIN lock scope boost::mutex::scoped_lock lock(*M); T = boost::thread(&f, M); }//END lock scope T.join(); std::cout << "finish\n"; }

On 02/14/2010 05:50 AM, Daniel Trebbien wrote:
Hello all,
I have been working on a project that uses the Boost Thread library, and I realized that there is a part of the code that needs to transfer a lock to a thread that has just been started. My idea for implementing this was to use a `boost::recursive_mutex` and pass a non-`const` reference to a `boost::recursive_mutex::scoped_lock` to the thread via `boost::ref`. Also, to ensure that the `scoped_lock` does not go out of scope until until the lock is moved, I thought to use a `boost::barrier`.
After writing some test code, I find it interesting that if I use a `recursive_mutex`, then an assertion fails, but if I use a `boost::mutex`, then the code seems to work fine.
Here is the test code that I have been experimenting with (the `recursive_mutex` version, which is also attached):
This leads me to wondering: 1. Is it correct and/or safe to move a lock to a new thread in this way?
2. Why does the `recursive_mutex` version fail and the `mutex` version succeed?
AFAIK, locking a mutex in one thread and unlocking it in the other leads to undefined behavior. See here: http://tinyurl.com/ydngxaq void unlock() Precondition: The current thread owns *this. Also, as Boost.Thread is modeled closely after pthreads, and what you're trying to do is also considered as error there: http://tinyurl.com/4uvub

Andrey and Seth, thank you both for your responses. After thinking about this more, I probably do not want to transfer the lock. `pthread_mutex_unlock` failing if the calling thread does not own the mutex makes sense to me now, and I am very glad, Andrey, that you linked me to the pthreads reference. Looking at the Windows equivalent, `ReleaseMutex`, the Microsoft documentation also says that the function fails if the calling thread does not own the mutex. The project that I am working on is a cross-platform template library for spawning child processes (http://code.google.com/p/libnstdcxx/source/browse/branches/experimental/nstd...). Incidentally, yes, I am aware of a number of other libraries that provide similar functionality (libexecstream, pstreams, APL, dlib, pexl, pclasses, Platinum, STLplus, and the Boost Process proposal), but I have found aspects of these that I do not like, including lack of wide character support, no Windows implementation, or an interface that encourages non-portable programming. The structure of the template that I am working on, which I am calling `basic_process`, is header-only to support an arbitrary allocator. There are platform data classes which contain platform-specific data that is necessary to create child processes (handles, process information structures, etc.), the cross-platform `basic_process` definition, and a platform implementation header that fills in the details of the `basic_process` member functions. For the Windows port, which I am working on first, the platform data class basically looks like: class basic_process_data { boost::mutex mutex; // handles and a `PROCESS_INFORMATION` bool running; // http://lists.boost.org/boost-users/2009/09/51813.php void start() { boost::mutex::scoped_lock lock(mutex); boost::thread(mem_fun5_t(&basic_process_data::runner), this, boost::ref(lock), ...); } void runner(boost::mutex::scoped_lock& lock_, ...) { // setup, call `CreateProcess`, wait for the child process to finish } }; Of course, I am going to remove the first parameter to the `runner` function because I will no longer be trying to move the `scoped_lock`. The idea for moving the lock to the new thread that is running `runner` was to have an elegant solution to the problem of preventing multiple "runner threads" from being created, which would all modify the `basic_process_data` member variables in a very non-thread-safe way. Perhaps what I will do now is just use the `running` variable, which is `true` when the thread is running, to prevent multiple runner threads as well as allow me to determine if the runner thread is running (per the suggestion at http://lists.boost.org/boost-users/2009/09/51813.php). Also, I recently discovered a Windows function called `RegisterWaitForSingleObject` which might obviate the creation of a thread that justs waits for the child process to finish. Daniel
participants (3)
-
Andrey Semashev
-
Daniel Trebbien
-
Seth Bunce