[thread] Question about MT API C++0X N2094

I'd like to know the rationale behind mutex concept requiring explicit locking signatures. Are users of the library "allowed" to access them directly? If yes why? Isn't this redundant to locks? Not requiring them, but letting them be implementation defined will allow the implementor more freedom. The only required concept should be: being able to take the address of the mutex. (Because this makes it unique.) E.g. the requirement for these N2094 mutex concepts would make implementation of my proposed POD mutex variant impossible. E.g. the current boost mutex also does not need to make these explicit functions. Sorry if I misunderstood the document. I would be glad if someone (H. Hinnant?) could explain to me. Thank you. Roland

Roland Schwarz <roland.schwarz@chello.at> writes:
I'd like to know the rationale behind mutex concept requiring explicit locking signatures.
Are users of the library "allowed" to access them directly? If yes why? Isn't this redundant to locks?
I guess that by "locks" you mean scoped_lock. Howard has answered this before: there are use cases where you don't want strict scoped locking, so yes, users are "allowed" to access the lock member functions directly.
Not requiring them, but letting them be implementation defined will allow the implementor more freedom.
I don't think we need that freedom.
E.g. the requirement for these N2094 mutex concepts would make implementation of my proposed POD mutex variant impossible.
No it wouldn't. PODs can have member functions. You would have to wrap your pointer in a struct, but that wouldn't stop it being POD. Yes, you wouldn't be able to use a plain 0 as an initializer, but you could use a static zero-initialized instance of the struct.
E.g. the current boost mutex also does not need to make these explicit functions.
Which leads to the mess that is boost::detail::lock_ops, to enable code outside of the scoped_lock implementation (e.g. cond var implementation) to access these functions. People also use things like boost::shared_ptr<boost::mutex::scoped_lock> to get round the restrictions. Anthony -- Anthony Williams Software Developer Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk

Anthony Williams wrote:
I guess that by "locks" you mean scoped_lock. Howard has answered this before: there are use cases where you don't want strict scoped locking, so yes, users are "allowed" to access the lock member functions directly.
No this isn't what I mean. mutex mx; mx.lock(); Why is this required, as well as: basic_lock lk(mx, defer_lock); lk.lock(); This is what I see as redundant. Btw.: I never understood why one wanted direct locking of a mutex on Boost.Thread. You always have been able to say: boost::mutex mx; boost::scoped_lock lk(mx,false); void foo (boost::scoped_lock* pl) { pl->lock(); pl->unlock(); } if you needed that behavior and doing away with the scoped feature. (Of course you need to take care not to do this from different threads since scoped_lock isn't MT safe.) It just isn't so obvious, which is a good thing, since scoped locking is far more safe. But it never prevented you from such practice. Also being able to lock directly on the mutex opens possibility to do such dangerous things as trying to lock/unlock from different threads. Requiring the ability to lock directly on the mutex object forces the implementation to be able to hold the entire state in the mutex object.
I don't think we need that freedom.
So you don't think my POD mutex of value? Sorry I think I misunderstand you. You already said differently, didn't you?
No it wouldn't. PODs can have member functions. You would have to wrap your pointer in a struct, but that wouldn't stop it being POD. Yes, you wouldn't be able to use a plain 0 as an initializer, but you could use a static zero-initialized instance of the struct.
Hmm, might indeed be possible. I'll try this :-)
E.g. the current boost mutex also does not need to make these explicit functions.
Which leads to the mess that is boost::detail::lock_ops, to enable code outside of the scoped_lock implementation (e.g. cond var implementation) to access these functions. People also use things like boost::shared_ptr<boost::mutex::scoped_lock> to get round the restrictions.
I don't think that this is the main reason why it was done this way (I might be false.) This is just to express common behavior. It is easy enough to access the mutex objects inner semaphore/CRITICAL_SECTION from the contained scoped_lock classes. But thank you for your explanations, I'll try to do the aggregate mutex with exposed locking and tell you my findings. Roland

Roland Schwarz wrote:
Btw.: I never understood why one wanted direct locking of a mutex on Boost.Thread. You always have been able to say:
boost::mutex mx; boost::scoped_lock lk(mx,false);
No, lk is explicitly documented as being not thread safe. When two threads attempt to lock lk at the same time, the behavior is undefined.

Peter Dimov wrote:
No, lk is explicitly documented as being not thread safe. When two threads attempt to lock lk at the same time, the behavior is undefined.
Yes I know this, and I also explicitly stated it. But on the other hand: A mutex must not be locked from one thread and unlocked from another too. True? So this (unscoped) usage is dangerous in any case, isn't it? Roland

Roland Schwarz wrote:
Peter Dimov wrote:
No, lk is explicitly documented as being not thread safe. When two threads attempt to lock lk at the same time, the behavior is undefined.
Yes I know this, and I also explicitly stated it.
Indeed, sorry for not paying attention. But the only point of locking a mutex is to be able to do that from two different threads at the same time. If you know that only one thread is accessing the scoped_lock at a time, you wouldn't need a scoped_lock in the first place.
But on the other hand: A mutex must not be locked from one thread and unlocked from another too. True?
True.
So this (unscoped) usage is dangerous in any case, isn't it?
True. The recommended way to lock a mutex is still via the scoped_lock. The direct interface is provided because when it's needed, there is no safer way to get it. Sometimes you just need to lock the mutex in one place and unlock it in another, and there is no way around it.

Peter Dimov wrote:
Indeed, sorry for not paying attention. But the only point of locking a mutex is to be able to do that from two different threads at the same time. If you know that only one thread is accessing the scoped_lock at a time, you wouldn't need a scoped_lock in the first place.
Could you please provide an example where it is not possible to solve the problem with scoped_lock only? I simply cannot find one. Currently this would be the only "killer argument" for my proposed aggregate/POD type mutex. Therfore I am very interested in this.
.... Sometimes you just need to lock the mutex in one place and unlock it in another, and there is no way around it.
Are you thinking the following kind of scenario? void foo() { scoped_lock lk(mx); bar(lk); } void bar(scoped_lock& lk) { lk.unlock(); } Roland

Roland Schwarz wrote:
Peter Dimov wrote:
Indeed, sorry for not paying attention. But the only point of locking a mutex is to be able to do that from two different threads at the same time. If you know that only one thread is accessing the scoped_lock at a time, you wouldn't need a scoped_lock in the first place.
Could you please provide an example where it is not possible to solve the problem with scoped_lock only? I simply cannot find one. Currently this would be the only "killer argument" for my proposed aggregate/POD type mutex. Therfore I am very interested in this.
The problem mainly arises when you have to implement an interface that has separate lock and unlock functions. class X { private: mutex mx_; public: void lock(); void unlock(); }; If you could change the interface of X and force its clients to use X::scoped_lock for locking, there wouldn't be a problem. But sometimes you can't; for example, you may be implementing a C API or a COM interface, and there is no way to impose scoped locks on their clients. If the entire program can be persuaded to use scoped locks, direct lock/unlock calls are probably never necessary (given movable locks, otherwise you can't return a lock from a function).

Peter Dimov wrote:
class X { private:
mutex mx_;
public:
void lock(); void unlock(); };
Ok, I understand. But how's about the (admittedly strange looking) solution: class X { private: mutex mx_; thread_specifc_ptr<mutex::scoped_lock> lk_; public: void lock() { if (0 == lk_) lk_.reset(new mutex::scoped_lock); lk_->lock(); } void unlock() { lk_->unlock(); } }; Hmm, I am not sure about the implications of thread_specific_ptr being used as a member. Will need to investigate... But in principle it is the same kind of trick I would need to apply if providing lock/unlock on my POD mutex type. I agree however, that a cleaner interface in this respect would be direct access to mutex.lock() mutex.unlock(). Roland

On Nov 1, 2006, at 2:34 PM, Roland Schwarz wrote:
Could you please provide an example where it is not possible to solve the problem with scoped_lock only? I simply cannot find one. Currently this would be the only "killer argument" for my proposed aggregate/POD type mutex. Therfore I am very interested in this.
The implementation of condition variables on Windows is an example of where you want to lock/unlock in a non-scoped pattern (if I'm understanding your question correctly).
Are users of the library "allowed" to access them directly? If yes why? Isn't this redundant to locks?
Yes. So clients can write their own mutexes and use them with std::locks (and vice-versa).
E.g. the requirement for these N2094 mutex concepts would make implementation of my proposed POD mutex variant impossible.
I think a statically initialized mutex is very important. I hope we can solve this one. Might take a language extension... -Howard

Howard Hinnant wrote:
I think a statically initialized mutex is very important. I hope we can solve this one. Might take a language extension...
Have you seen my proposal of a POD/aggregate type mutex? This will work without a language change. However I've to admit I had no review responses about the prototype implementation yet. Roland

On Nov 1, 2006, at 4:53 PM, Roland Schwarz wrote:
Howard Hinnant wrote:
I think a statically initialized mutex is very important. I hope we can solve this one. Might take a language extension...
Have you seen my proposal of a POD/aggregate type mutex? This will work without a language change.
I'm sorry to say I haven't. I'm doing too many jobs at the moment and none of them well. But that doesn't mean I don't think such a proposal is important to flesh out. I appreciate your work in this area. -Howard

Roland Schwarz <roland.schwarz@chello.at> writes:
Anthony Williams wrote:
I guess that by "locks" you mean scoped_lock. Howard has answered this before: there are use cases where you don't want strict scoped locking, so yes, users are "allowed" to access the lock member functions directly.
No this isn't what I mean.
mutex mx; mx.lock();
Why is this required, as well as:
basic_lock lk(mx, defer_lock); lk.lock();
This is what I see as redundant.
They perform different duties. mx.lock() is a low-level interface. basic_lock is higher level, and automatically unlocks in its destructor, if locked.
Btw.: I never understood why one wanted direct locking of a mutex on Boost.Thread. You always have been able to say:
boost::mutex mx; boost::scoped_lock lk(mx,false);
void foo (boost::scoped_lock* pl) { pl->lock();
pl->unlock(); }
if you needed that behavior and doing away with the scoped feature. (Of course you need to take care not to do this from different threads since scoped_lock isn't MT safe.)
As you just said, scoped_lock isn't MT safe, so you can't use it to emulate true public lock member functions on the mutex.
It just isn't so obvious, which is a good thing, since scoped locking is far more safe. But it never prevented you from such practice.
It makes it harder than necessary.
Also being able to lock directly on the mutex opens possibility to do such dangerous things as trying to lock/unlock from different threads.
Yes. That's why we must provide the scoped_lock stuff too. If we provide the safe option, and make it easy to use, then most people will use it. If people don't, and they get into difficulty, the first suggestion should be that they use the easy and safe interface instead.
Requiring the ability to lock directly on the mutex object forces the implementation to be able to hold the entire state in the mutex object.
No it doesn't. A named mutex scheme doesn't need to store *any* information in the mutex object, however the lock functions are accessed.
I don't think we need that freedom.
So you don't think my POD mutex of value? Sorry I think I misunderstand you. You already said differently, didn't you?
Yes, you misunderstood. I think a POD mutex is of value. Anthony -- Anthony Williams Software Developer Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk

Roland Schwarz wrote:
if you needed that behavior and doing away with the scoped feature. (Of course you need to take care not to do this from different threads since scoped_lock isn't MT safe.) It just isn't so obvious, which is a good thing, since scoped locking is far more safe. But it never prevented you from such practice. Also being able to lock directly on the mutex opens possibility to do such dangerous things as trying to lock/unlock from different threads.
Requiring the ability to lock directly on the mutex object forces the implementation to be able to hold the entire state in the mutex object.
I don't think we need that freedom.
I think the main reason for the approach of direct locking is the need to establish a simple scoped locking framework independently from the mutex implementation. Currently in Boost.Thread the mutex must somehow give access to scoped lock to use the locking functions (friend or whatever). N2094 wants to design a locking framework just like the STL algorithms. Mutexes would be like containers, with public interfaces and well known semantics. Scoped locks would be the equivalent to STL algorithms. Algorithms and Containers don't know about each other, but any Container that has the necessary public members can be used with an STL algorithm. This approach allows the user to construct more locking utilities and synchronization utilities easily. I don't know if this was the answer you were finding, is it? Regards, Ion

Ion Gaztañaga wrote:
I think the main reason for the approach of direct locking is the need to establish a simple scoped locking framework independently from the mutex implementation. Currently in Boost.Thread the mutex must somehow give access to scoped lock to use the locking functions (friend or whatever). N2094 wants to design a locking framework just like the STL algorithms.
Mutexes would be like containers, with public interfaces and well known semantics. Scoped locks would be the equivalent to STL algorithms. Algorithms and Containers don't know about each other, but any Container that has the necessary public members can be used with an STL algorithm. This approach allows the user to construct more locking utilities and synchronization utilities easily.
I don't know if this was the answer you were finding, is it?
Yes I understand, but this framework forces the mutex object to contain state. Please don't get me wrong I know that a mutex needs to hold some state, but not necessarily the mutex object itself. A mutex has two aspects ID and state. I believe you can store state elsewhere but you can't forgo ID. Roland

Roland Schwarz <roland.schwarz@chello.at> writes:
Yes I understand, but this framework forces the mutex object to contain state.
I don't understand where this assertion is coming from. Anthony -- Anthony Williams Software Developer Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk

Anthony Williams wrote:
Roland Schwarz <roland.schwarz@chello.at> writes:
Yes I understand, but this framework forces the mutex object to contain state.
I don't understand where this assertion is coming from.
It's an interesting point. A mutex can keep track of its scoped_lock objects in a list in order to implement locking/unlocking. This makes a direct lock/unlock interface hard to implement since there are no scoped_lock objects to put on the list.

"Peter Dimov" <pdimov@mmltd.net> writes:
Anthony Williams wrote:
Roland Schwarz <roland.schwarz@chello.at> writes:
Yes I understand, but this framework forces the mutex object to contain state.
I don't understand where this assertion is coming from.
It's an interesting point. A mutex can keep track of its scoped_lock objects in a list in order to implement locking/unlocking. This makes a direct lock/unlock interface hard to implement since there are no scoped_lock objects to put on the list.
OK, now I see where Roland is coming from, as his POD mutex does just that. Anthony -- Anthony Williams Software Developer Just Software Solutions Ltd http://www.justsoftwaresolutions.co.uk

Anthony Williams wrote:
OK, now I see where Roland is coming from, as his POD mutex does just that.
Exactly :-) I just tried to extend, like you were suggesting, my POD mutex as an aggregate, i.e.: struct mutex { impldef-type * p; }; Works, fine. Ok next step: struct mutex { void lock() { .... } void unlock { .... } ... various other POD's to hold queue entry ... implde-type * p; }; Ok. The lock unlock are no problem, but "... various other POD's to hold queue entry ..." need to be thread specific! I did not yet put this to code, but it is a disprove of my original believe that it is unfeasible to provide lock unlock to my POD/aggregate mutex. So it would be feasible, but locking on the mutex would be a bit slower due to the thread_specific_ptr. But this puts some burden on the thread_specific pointer too: It also must be static initializeable! So it cannot be the boost one. Roland

I have two more questions: 1) No mention of thread local storage. Why? 2) Is the basic_lock meant to be thread safe or not? Roland

On Nov 1, 2006, at 5:10 PM, Roland Schwarz wrote:
I have two more questions:
1) No mention of thread local storage. Why?
I believe it will be a language feature. My vote for keyword is thread_local. Lawrence Crowl is taking it on.
2) Is the basic_lock meant to be thread safe or not?
Not. And basic_lock is really not a template class, just a concept. <on the difference between locks and mutexes> So one of the things I found really confusing about boost::scoped_lock, and I only found it confusing after several years of using it, is: bool scoped_locked::locked() const; Bad name. But really subtly bad. This function does not tell you if the underlying mutex is locked or not. It tells you whether this scoped_lock owns the mutex's locked state. locked() could answer false and the mutex might still be locked (just by someone else). So I think this is a better name for the same functionality: bool scoped_locked::owns() const; After this, at least for me, the important differences between mutexes and locks slowly became a little clearer: mutex lock * Is construction/destruction thread safe? no no * Is lock()/unlock() thread safe? yes no * Is it moveable? no yes mutex is nothing more than a handle to an OS resource (like memory). lock is an RAII wrapper for the mutex (as smart pointers are to memory). Unlike memory, locks handle the lock/unlock state of the mutex, rather than the allocation/dellocation. lock is essentially an auto_ptr to a mutex, but does lock() on construction and unlock() on destruction. Despite the existence of lock, being able to operate directly on the mutex is still occasionally useful (as is using new/ delete directly on heap allocated memory). All of what I've said above is superficially obvious. But, imho, it bears repeating and contemplation even for experienced users. It is all too easy to confuse the role of locks and mutexes (as I have done myself, quite recently). </on the difference between locks and mutexes> -Howard

Roland Schwarz wrote:
1) No mention of thread local storage. Why?
One reason is that we hope that the language will provide it. Another is that I haven't had time to prototype my ideas for a thread_specific<X> template. There might be no need, depending on the evolution of the __thread proposal. We'll just write __thread X x; (possibly using another keyword.)
participants (5)
-
Anthony Williams
-
Howard Hinnant
-
Ion Gaztañaga
-
Peter Dimov
-
Roland Schwarz