
Looking through the archives, a number of requests have been made for a null_mutex, but none exist. Probably because of the chance of trouble when used in conjunction with a condition variable. However, I have need for one (and it looks like others do as well, with a simple google search). Attached, are two files, each representing the output of running cvs diff on my local CVS repository (which is the official 1.32 release). diff.txt is the result of running: cvs -q diff diff-c.txt is the result of running: cvs -q diff -c I'd appreciate any feedback, and also consideration to add something like this to Boost.Thread.

On Thu, 9 Jun 2005 12:56:55 -0400 Jody Hagins <jody-boost-011304@atdesk.com> wrote: Oops. I forgot to add this... Notice one obvious issue: notify_one() and notify_all() still go through the native CV code to ping potential waiting threads. However, without significant change to the current implementation of condition, I do not see an easy way to prevent this. Initially, I added condition_t<>, and specialized condition_t<void> to be identical to the current implementation of condition, condition_t<null_mutex> has no-ops for notify_*(), and the default implementation of condition_t<typename Lock> is identical to condition_t<void>, except the member functions are not templatized on the lock type (since the class is already templatized that way). Then, we also have... typedef condition_t<void> condition; which should leave all existing use of condition, working the same. However, this is a lot of change, for an optimization (calling notify_*() should not break anything), and I wanted to present as small of a change as possible. If this is accepted, then it may be worth adding the optimization, and then writing tests to make sure it does not break anything that uses condition in its current state...

Jody Hagins wrote:
Notice one obvious issue: notify_one() and notify_all() still go through the native CV code to ping potential waiting threads. However, without significant change to the current implementation of condition, I do not see an easy way to prevent this.
How is a condition variable supposed to behave when used with a null mutex? Is this a legitimate use case?

On Fri, 10 Jun 2005 22:14:41 +0300 "Peter Dimov" <pdimov@mmltd.net> wrote:
Jody Hagins wrote:
Notice one obvious issue: notify_one() and notify_all() still go through the native CV code to ping potential waiting threads. However, without significant change to the current implementation of condition, I do not see an easy way to prevent this.
How is a condition variable supposed to behave when used with a null mutex? Is this a legitimate use case?
I believe it is a valid use case. Consider code for something like a popping a message off a message queue. In such a case, the pop() member function would block until the message queue was not empty. In a single-thread case (i.e., using null_mutex), you would expect some kind of exception, because you can not call pop with an empty queue in a single threaded case. while (this->is_empty_()) { if (this->is_shutdown_()) { throw shutdown_exception(); } if (!this->is_get_active_()); { throw get_inactive_exception(); } auto_increment num_waiting(this->num_waiting_consumers_); if (timeout) { if (!this->waiting_consumers_cv_.timed_wait(lock, *timeout)) { throw timeout_exception(); } } else { this->waiting_consumers_cv_.wait(lock); } } When lock is a normal mutex, the locking mechanism and the condition variable work fine. If the lock is a null_mutex, all the code works fine as well, but when it comes time to block, a thread_would_block() exception is thrown from within the wait() or timed_wait() method of the condition variable. Thus, none of the code that uses locks and condition variables needs to change, and ONE of several "right things" happens, namely an exception is thrown, indicating that the thread would block. If you use a null_mutex, you are not expecting to block on a condition variable, because you better have all the data you need.

I realized that the previous code snippet may be hard to follow, since it was taken out of the middle of a member function. So, here if the member function, in its entirety. Basically, consumer_wait_() is an internal method of a message queue, and would be called from within any get/pop function (i.e., any time a consumer asks for a piece of data off the message queue). When consumer_wait_() returns, the lock is still held, and there is at least one message in the queue. If, for some reason, the consumer can not get the message, an exception is thrown. In this code, an exception can be thrown: 1. the message queue has been shutdown 2. the message queue has been deactivated for GET operations 3. the timeout elapses while waiting for a message to arrive 4. timed_wait() or wait() throws: 1. an internal error in the condition variable/lock will generate a lock_error() exception 2. a then thread_would_block() exception is generated if the mutex type is null_mutex. I guess all code could specialize on null_mutex, but I think what I have added to boost::condition is reasonable for most uses (and the really special ones can always specialize themselves). template <typename Lock> void consumer_wait_( Lock & lock, boost::xtime const * timeout) { struct auto_increment : private boost::noncopyable { auto_increment(unsigned int & t) : t_(t) { ++t_; } ~auto_increment() { --t_; } private: unsigned int & t_; }; #ifndef NDEBUG struct assertions : private boost::noncopyable { assertions( Lock const & lock, size_type const & msgcnt) : lock_(lock) , msgcnt_(msgcnt) { assert(this->lock_.locked()); } ~assertions() { assert(this->lock_.locked()); assert(this->msgcnt_ > 0); } Lock const & lock_; size_type const & msgcnt_; }; assertions assertions(lock, this->msgcnt_); #endif // NDEBUG while (this->is_empty_()) { if (this->is_shutdown_()) { throw shutdown_exception(); } if (!this->is_get_active_()); { throw get_inactive_exception(); } auto_increment num_waiting(this->num_waiting_consumers_); if (timeout) { if (!this->waiting_consumers_cv_.timed_wait(lock, *timeout)) { throw timeout_exception(); } } else { this->waiting_consumers_cv_.wait(lock); } } if (!this->is_get_active_()) { throw get_inactive_exception(); } }

Jody Hagins wrote:
On Fri, 10 Jun 2005 22:14:41 +0300 "Peter Dimov" <pdimov@mmltd.net> wrote:
How is a condition variable supposed to behave when used with a null mutex? Is this a legitimate use case?
I believe it is a valid use case. Consider code for something like a popping a message off a message queue. In such a case, the pop() member function would block until the message queue was not empty. In a single-thread case (i.e., using null_mutex), you would expect some kind of exception, because you can not call pop with an empty queue in a single threaded case.
Let's take a step back. Why would I use the message queue at all in a single threaded program? Its whole purpose is to enable inter-thread communication, right?

On Sat, 11 Jun 2005 00:34:08 +0300 "Peter Dimov" <pdimov@mmltd.net> wrote:
Let's take a step back. Why would I use the message queue at all in a single threaded program? Its whole purpose is to enable inter-thread communication, right?
How about another step back. The initial query was about using mutex and condvar together when null_mutex is defined. Obviously, if you are doing this, you are trying to write generic code, which can be used in both a single and multi threaded environment (otherwise, why even care about null_mutex). As one example, I suggested a message queue (there are many other examples of writing generic code for both single and multi threaded environments). If we can agree that writing the same code for single and multi threaded applications is a good thing (or at least, not a terrible thing), then we can see the use of null_mutex. However, in some cases, code will use a condition variable. We do not want to disqualify compilation of this code, yet we know that if the code path is executed in a single threaded environment (or "unlocking" environment), it would be a "bad thing" to wait. Thus, I think throwing an exception is a reasonable course of action if a condition variable is waited upon with a null_mutex. Now, taking one step forward from there (and one back from where I was prior to this email), I should be right where you want me ;-) Why would I want to use a message queue in a single-threaded application? I have several answers, but the biggest one that relates to this thread (and boost), is code reuse. Consider an application written to a user level model similar to SVr4 streams. Each "module" process messages off a message queue. You can write all application modules using the same interface. Also, consider a network application. If your "reader" modules are written to put their data on a message queue, then you can easily chain protocol layers. If you are using a single thread or multiple threads, you still use the same code. If you are multithreaded, another thread will slurp up the message and process it as soon as it is available (and the condvar is signalled, and the lock is released). If you are single threaded, the message will be processed when the controling code path decides to process the queue. In either case, the main processing code is the same, and the application specific module code is the same, providing a nice bit of code reuse. Yes, there is a little overhead in the message queue, but even in a multithreaded environment, you should not be putting heavy weight messages on the queue.

On 6/11/05, Peter Dimov <pdimov@mmltd.net> wrote: Let's take a step back. Why would I use the message queue at all in a single threaded program? Its whole purpose is to enable inter-thread communication, right?
One way to look at it is that a mq just provides a quality of service implementation detail for message passing. Message passing can be quite generic, in small talk function / method invocation is referred to as message passing. Buffering alone might be a reason rather than inter-thread communication. One day a really useful lib that supports abstraction of the end points and quality of service for message passing that resolves to a function call, internal process message queue, inter-process mechanism, inter machine mechanism and the like might appear. Being able to abstract the common locking mechanisms for this is important. It is my recurring thought that the named parameter library, serialisation lib, an improved concurrency lib and a socket lib have an important role to play in a generic architecture lib that supports such message passing abstractions that will allow us to build some pretty innovative flexibility in our designs. The concept of a zero overhead null mutex is essential to this. matt.

On Thu, 9 Jun 2005 21:41:31 +0200 "Pavel Vozenilek" <pavel_vozenilek@hotmail.com> wrote:
"Jody Hagins" wrote:
Looking through the archives, a number of requests have been made for a null_mutex, but none exist.
An implementation exists in Shmem library (in Sandbox files).
OK, thanks. A few comments... Your class is missing: scoped_timed_lock void do_lock(cv_state &); void do_unlock(cv_state &); which is probably not a big deal if it only used for shmem, and only used with shmem::shared_condition. However, you would not be able to use this class as a drop-in replacement for any of the other thread::*mutex classes, because it would require reasonable interoperability with boost::condition.

Hi Jody, Shmem null mutex was just a fast implementation mainly to avoid mutex overhead with shmem machinery when used with non-shared memory buffers, so I if boost::thread, accepts a null mutex, I would happily use that. I haven't checked your patches, but I would expect a zero overhead mutex, regarding its size (eliminated using empty base optimization) and code generation (lock and unlock are empty functions). Regards, Ion ----- Original Message ----- From: "Jody Hagins" <jody-boost-011304@atdesk.com> To: <boost@lists.boost.org> Sent: Thursday, June 09, 2005 10:03 PM Subject: Re: [boost] Re: [thread] null_mutex
On Thu, 9 Jun 2005 21:41:31 +0200 "Pavel Vozenilek" <pavel_vozenilek@hotmail.com> wrote:
"Jody Hagins" wrote:
Looking through the archives, a number of requests have been made for a null_mutex, but none exist.
An implementation exists in Shmem library (in Sandbox files).
OK, thanks. A few comments...
Your class is missing: scoped_timed_lock void do_lock(cv_state &); void do_unlock(cv_state &);
which is probably not a big deal if it only used for shmem, and only used with shmem::shared_condition.
However, you would not be able to use this class as a drop-in replacement for any of the other thread::*mutex classes, because it would require reasonable interoperability with boost::condition.

On Fri, 10 Jun 2005 07:03:58 +0200 Ion Gaztañaga <ion_g_m@terra.es> wrote:
Hi Jody,
Shmem null mutex was just a fast implementation mainly to avoid mutex overhead with shmem machinery when used with non-shared memory buffers, so I if boost::thread, accepts a null mutex, I would happily use that. I haven't checked your patches, but I would expect a zero overhead mutex, regarding its size (eliminated using empty base optimization) and code generation (lock and unlock are empty functions).
I think it satisfies your requirement, but remember when using it with a lock (e.g., scoped_lock), the lock itself is not empty, because it has to preserve the requirements on the lock (e.g., knowing if it is in a locked state). Also, as noted earlier, calling notify_one() and notify_all() on the condition incur overhead (currently), even when used in conjunction with a null_mutex.

Oops... I sent the other one with diffs from the wrong workspace, and there's a few minor differences (missing xtime in do_timed_wait(), and different exception name)...
participants (5)
-
Ion Gaztañaga
-
Jody Hagins
-
Matt Hurd
-
Pavel Vozenilek
-
Peter Dimov