[Boost.Thread] mutex Win32 implementation changes

I've just checked in changes to Win32 implementations of the mutex and recursive_mutex classes. They now use a Win32 critical section instead of a mutex whenever possible for the normal and try_ variations, though not for the timed_ variations. These are changes that were started on the mutex classes quite some time back on the thread_dev branch; I've finished the changes and applied them to the recursive_mutex classes as well. In addition, the thread_dev branch has code that allows a mutex to be named, which allows it to be shared between two or more processes. This change was begun by Bill Kempf, but is only implemented for Win32. Any suggestions about the best way to add this feature to the pthreads and MPTasks implementations? Mike

Michael Glassford wrote:
I've just checked in changes to Win32 implementations of the mutex and recursive_mutex classes. They now use a Win32 critical section instead
MS critical section (current implementation) doesn't perform well if there is contention. They handoff ownership. You can build more efficient mutex (with all three lock/trylock/timedlock operations) using atomic swap/xchg and auto-reset event. It will work on 386 (no CAS required)... but I just can't stop scratching my head over mysterious lack of xchg.rel on Itanic (they only have xchg.acq). regards, alexander.

Alexander Terekhov wrote:
Michael Glassford wrote:
I've just checked in changes to Win32 implementations of the mutex and recursive_mutex classes. They now use a Win32 critical section instead
MS critical section (current implementation) doesn't perform well if there is contention.
So I've heard.
They handoff ownership. You can build more efficient mutex (with all three lock/trylock/timedlock operations) using atomic swap/xchg and auto-reset event.
I hope to look into this at some point, but for now have higher-priority items on my Boost.Threads task list. Do you have a full-fledged implementation for such a beast? I remember you posting sample code some time back, but don't remember how complete it was.
It will work on 386 (no CAS required)... but I just can't stop scratching my head over mysterious lack of xchg.rel on Itanic (they only have xchg.acq).
regards, alexander.
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost

Michael Glassford wrote: [...]
Do you have a full-fledged implementation for such a beast?
Trade secret.
I remember you posting sample code some time back, ...
class swap_based_mutex_for_windows { // noncopyable atomic<int> m_lock_status; // 0: free, 1/-1: locked/contention auto_reset_event m_retry_event; public: // ctor/dtor [w/o lazy event init] void lock() throw() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); } bool trylock() throw() { return !m_lock_status.swap(1, msync::acq) ? true : !m_lock_status.swap(-1, msync::acq); } bool timedlock(absolute_timeout const & timeout) throw() { if (m_lock_status.swap(1, msync::acq)) { while (m_lock_status.swap(-1, msync::acq)) if (!m_retry_event.timedwait(timeout)) return false; } return true; } void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); } }; regards, alexander.

Alexander Terekhov wrote:
Michael Glassford wrote: [...]
Do you have a full-fledged implementation for such a beast?
Trade secret.
I remember you posting sample code some time back, ...
class swap_based_mutex_for_windows { // noncopyable
atomic<int> m_lock_status; // 0: free, 1/-1: locked/contention auto_reset_event m_retry_event;
public:
// ctor/dtor [w/o lazy event init]
void lock() throw() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); }
bool trylock() throw() { return !m_lock_status.swap(1, msync::acq) ? true : !m_lock_status.swap(-1, msync::acq); }
bool timedlock(absolute_timeout const & timeout) throw() { if (m_lock_status.swap(1, msync::acq)) { while (m_lock_status.swap(-1, msync::acq)) if (!m_retry_event.timedwait(timeout)) return false; } return true; }
void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); }
};
Looking at this again, it's not entirely clear to me what the return value of atomic<int>::swap() is. Can you clarify? Thanks, Mike

Alexander Terekhov wrote:
Michael Glassford wrote: [...]
Looking at this again, it's not entirely clear to me what the return value of atomic<int>::swap() is. Can you clarify?
Old value. InterlockedExchange. Brrr.
Thanks. That's what I thought, but I wanted to make sure. Mike

Michael Glassford wrote: [...]
Thanks. That's what I thought, but I wanted to make sure.
Note that this illustration/example doesn't provide "posix safety" with respect to mutex destruction if you use try/timed operations on it. "posix safety" is needed for things like lock-protected ref counting when mutex is "part of" managed object (i.e. you destroy the mutex when the count drops to zero). Another thing to watch is MS event's behavior for timedout waits. Reportedly, on some windows version(s), *timedout* waiter on an event/sema may still consume a signal (such behavior is OK and even desirable for condvars because timedout status there is just an indication of an independent event, but it's NOT OK for events/semas because they have state). If/when this is the case, the timedout waiter on a "retry_event" must try to acquire the mutex (with "-1") and, if succeeded, either unlock it and report failure (which makes little sense given "watchdog nature" of mutex.timedwait) or just ignore timedout status and report success. regards, alexander.

Alexander Terekhov wrote:
Michael Glassford wrote: [...]
Thanks. That's what I thought, but I wanted to make sure.
Note that this illustration/example doesn't provide "posix safety" with respect to mutex destruction if you use try/timed operations
^^^^^^^^^^^^^^^^^^^^ Err. This CAS/TID-less thing is not safe at all with respect to "posix safety" for mutex destruction.
on it. "posix safety" is needed for things like lock-protected ref counting when mutex is "part of" managed object (i.e. you destroy the mutex when the count drops to zero). Another thing to watch is MS event's behavior for timedout waits. Reportedly, on some windows version(s), *timedout* waiter on an event/sema may still consume a signal (such behavior is OK and even desirable for condvars because timedout status there is just an indication of an independent event, but it's NOT OK for events/semas because they have state). If/when this is the case, the timedout waiter on a "retry_event" must try to acquire the mutex (with "-1") and, if succeeded, either unlock it and report failure (which makes little sense given "watchdog nature" of mutex.timedwait) or just ignore timedout status and report success.
regards, alexander.

< This is a "reply" to a couple of "off-band" emails > Alexander Terekhov wrote: [...]
Note that this illustration/example doesn't provide "posix safety" with respect to mutex destruction if you use try/timed operations ^^^^^^^^^^^^^^^^^^^^
Err. This CAS/TID-less thing is not safe at all with respect to "posix safety" for mutex destruction.
It can be made safe. The simplest way is to "pimpl" it and never destroy the "impl" thing (reusing it is OK). Other (more space and performance efficient) solutions to it are also possible, but they are kind of tricky. Ideally, MS should patch their OSes and provide a "thread queue" (Linux folks reinvented "half-or-less" of this thing and proudly called it "a futex") parking interface that would allow fast sync. For mutexes, (high order waiters bit shall be maintained by the kernel) mutex_lock: WHILE atomic_bit_test_set_ddacq(&lock, 1) lock_queue_wait(&lock, 1) // wait if locked bit is set mutex_unlock: uintptr_t lock_queue; IF atomic_decrement_rel(lock_queue = &lock) THEN lock_queue_wake(lock_queue, 1) For semas, sema_lock: WHILE // CAS/LL-SC !atomic_decrement_if_binand_7FFFFFFF_is_not_zero_ddacq(&lock) lock_queue_wait(&lock, 0) // wait if sem.value is zero sema_unlock: uintptr_t lock_queue; IF atomic_increment_rel(lock_queue = &lock) > 0x80000000 THEN lock_queue_wake(lock_queue, 1) (try/timed operations omitted for brevity) There shall be no requirement to "open/close" that parking mechanism (physical address shall be used as "queue-id"/"wait token"). And it shall be safe to pass an "invalid" (even unmapped/nonexistent) queue-id/virtual address to a wake call... in worse case it would result in spurious wakes but spurious wakes shall be allowed anyway.
on it. "posix safety" is needed for things like lock-protected ref counting when mutex is "part of" managed object (i.e. you destroy the mutex when the count drops to zero). Another thing to watch is MS event's behavior for timedout waits. Reportedly, on some windows version(s), *timedout* waiter on an event/sema may still consume a signal (such behavior is OK and even desirable for condvars because timedout status there is just an indication of an independent event, but it's NOT OK for events/semas because they have state). If/when this is the case, the timedout waiter on a "retry_event" must try to acquire the mutex (with "-1") and, if succeeded, either unlock it and report failure (which makes little sense given "watchdog nature" of mutex.timedwait) or just ignore timedout status and report success.
regards, alexander.

Alexander Terekhov wrote:
Michael Glassford wrote: [...]
Thanks. That's what I thought, but I wanted to make sure.
Note that this illustration/example doesn't provide "posix safety" with respect to mutex destruction if you use try/timed operations on it. "posix safety" is needed for things like lock-protected ref counting when mutex is "part of" managed object (i.e. you destroy the mutex when the count drops to zero).
No links? What does "posix safety" mean? The ability to destroy a locked mutex? To unlock a destroyed mutex? Is a CRITICAL_SECTION "posix-safe"? A HMUTEX?
Another thing to watch is MS event's behavior for timedout waits. Reportedly, on some windows version(s), *timedout* waiter on an event/sema may still consume a signal (such behavior is OK and even desirable for condvars because timedout status there is just an indication of an independent event, but it's NOT OK for events/semas because they have state).
Which versions?
If/when this is the case, the timedout waiter on a "retry_event" must try to acquire the mutex (with "-1") and, if succeeded, either unlock it and report failure (which makes little sense given "watchdog nature" of mutex.timedwait) or just ignore timedout status and report success.
Pseudocode? ;-)

Peter Dimov wrote: [...]
Note that this illustration/example doesn't provide "posix safety" with respect to mutex destruction if you use try/timed operations on it. "posix safety" is needed for things like lock-protected ref counting when mutex is "part of" managed object (i.e. you destroy the mutex when the count drops to zero).
No links? What does "posix safety" mean? The ability to destroy a locked mutex?
The ability to destroy unlocked mutex... in spite of having some thread(s) still not returned from unlock() call. For example, posix barrier doesn't provide similar kind of safety -- you can't safely destroy a barrier on return of PTHREAD_BARRIER_SERIAL_THREAD (or any other thread for that matter), only after *ALL* threads have returned from the wait call. POSIX mutexes and condvars are "safer" in this respect.
To unlock a destroyed mutex? Is a CRITICAL_SECTION "posix-safe"? A HMUTEX?
I have no idea. Well, the following advice is quite reasonable, I guess. (you might want to read this entire thread ;-) ) http://groups.google.com/groups?selm=4087a6d3%40usenet01.boi.hp.com (Subject: Re: pthread_mutex_delete on almost-used mutex?) ---- [...] beware that this is a tricky area of implementation, and you are not entirely unlikely to run into some implementation that's done it wrong by referencing the mutex (e.g., to record performance or debugging information) "on the way out" of the lock operation. While you'd end up with a valid bug report and perhaps a sense of personal justification, you'd still have a program that wouldn't work on that platform. Sometimes, you can't reasonably avoid dynamic and disposable locks; however as a matter of porting pragmatics you're better off sticking to static locks if you can, or to cache and save your "preowned" locks rather than destroying them. ----
Another thing to watch is MS event's behavior for timedout waits. Reportedly, on some windows version(s), *timedout* waiter on an event/sema may still consume a signal (such behavior is OK and even desirable for condvars because timedout status there is just an indication of an independent event, but it's NOT OK for events/semas because they have state).
Which versions?
"single CPU W2000" and maybe something else. I'll forward to you some details.
If/when this is the case, the timedout waiter on a "retry_event" must try to acquire the mutex (with "-1") and, if succeeded, either unlock it and report failure (which makes little sense given "watchdog nature" of mutex.timedwait) or just ignore timedout status and report success.
Pseudocode? ;-)
Sure. It's the same problem as the NPTL one illustrated here: ("-1" has the same meaning as NPTL's "2") http://groups.google.com/groups?selm=3F9AC8BC.1FEDE451%40web.de&output=gplain http://groups.google.com/groups?selm=x74qxsrde4.fsf%40bolo.xenadyne.com regards, alexander.

Alexander Terekhov wrote: [...]
http://groups.google.com/groups?selm=4087a6d3%40usenet01.boi.hp.com (Subject: Re: pthread_mutex_delete on almost-used mutex?)
---- [...] beware that this is a tricky area of implementation, and you are not entirely unlikely to run into some implementation that's done it wrong by referencing the mutex (e.g., to record performance or debugging information) "on the way out" of the lock operation. ^ | un-------------------------------------+
With respect to swap_based_mutex_for_windows thing (without pimpl-and-never-destroyed-impl), the problem is that it can go boom in unlock(). void lock() throw() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); } void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); } Scenario... Given: mutex protected refcounting. Two threads, A and B. t0: thread A locks the mutex and decrements refcount to 2; t1: thread B does m_lock_status.swap(1, msync::acq) on the fast path and sees 1; t2: thread A unlocks the mutex (doesn't enter slow path); t3: thread B does mutex m_lock_status.swap(-1, msync::acq), locks the mutex, decrements refcount to 1, does m_lock_status.swap(0, msync::rel) and enters slow path in unlock(); t4: thread A locks the mutex, decrements refcount to 0, unlocks the mutex, and destroys it (including event); t5: thread B goes BOOM. See also: http://lists.boost.org/MailArchives/boost/msg67104.php unlock { if (!--count ) { lock.release(); // throw()-nothing tsd::set(&key, false); } } Note that if you need "posix safety" with respect to unlock/destroy of this lock, "tsd::set(&key, false);" shall be done before "lock.release();" (and of course the underlying nonrecursive lock itself shall be safe with respect to unlock/destroy). --- regards, alexander.

Alexander Terekhov wrote:
With respect to swap_based_mutex_for_windows thing (without pimpl-and-never-destroyed-impl), the problem is that it can go boom in unlock().
void lock() throw() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); }
void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); }
Scenario...
Given: mutex protected refcounting. Two threads, A and B.
t0: thread A locks the mutex and decrements refcount to 2;
t1: thread B does m_lock_status.swap(1, msync::acq) on the fast path and sees 1;
t2: thread A unlocks the mutex (doesn't enter slow path);
t3: thread B does mutex m_lock_status.swap(-1, msync::acq), locks the mutex, decrements refcount to 1, does m_lock_status.swap(0, msync::rel) and enters slow path in unlock();
SetEvent( e_ );
t4: thread A locks the mutex, decrements refcount to 0, unlocks the mutex, and destroys it (including event);
CloseHandle( e_ );
t5: thread B goes BOOM.
Does it? The Win32 kernel is pretty resilient. ;-) We'll have to try it, I guess.

Peter Dimov wrote:
Alexander Terekhov wrote:
With respect to swap_based_mutex_for_windows thing (without pimpl-and-never-destroyed-impl), the problem is that it can go boom in unlock().
void lock() throw() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); }
void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); }
Scenario...
Given: mutex protected refcounting. Two threads, A and B.
t0: thread A locks the mutex and decrements refcount to 2;
t1: thread B does m_lock_status.swap(1, msync::acq) on the fast path and sees 1;
t2: thread A unlocks the mutex (doesn't enter slow path);
t3: thread B does mutex m_lock_status.swap(-1, msync::acq), locks the mutex, decrements refcount to 1, does m_lock_status.swap(0, msync::rel) and enters slow path in unlock();
SetEvent( e_ );
Nah, it suspends right before it.
t4: thread A locks the mutex, decrements refcount to 0, unlocks the mutex, and destroys it (including event);
CloseHandle( e_ );
and operator delete() [with subsequent "unmap"].
t5: thread B goes BOOM.
Does it? The Win32 kernel is pretty resilient. ;-) We'll have to try it, I guess.
First problem is access to freed memory. But even of you save event handle on the stack, you risk to break something because the same handle might now point to a new event object... in something that can't handle spurious wakes on it. regards, alexander.

Alexander Terekhov wrote:
Peter Dimov wrote:
Alexander Terekhov wrote:
With respect to swap_based_mutex_for_windows thing (without pimpl-and-never-destroyed-impl), the problem is that it can go boom in unlock().
void lock() throw() { if (m_lock_status.swap(1, msync::acq)) while (m_lock_status.swap(-1, msync::acq)) m_retry_event.wait(); }
void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); }
Scenario...
Given: mutex protected refcounting. Two threads, A and B.
t0: thread A locks the mutex and decrements refcount to 2;
t1: thread B does m_lock_status.swap(1, msync::acq) on the fast path and sees 1;
t2: thread A unlocks the mutex (doesn't enter slow path);
t3: thread B does mutex m_lock_status.swap(-1, msync::acq), locks the mutex, decrements refcount to 1, does m_lock_status.swap(0, msync::rel) and enters slow path in unlock();
SetEvent( e_ );
Nah, it suspends right before it.
t4: thread A locks the mutex, decrements refcount to 0, unlocks the mutex, and destroys it (including event);
CloseHandle( e_ );
and operator delete() [with subsequent "unmap"].
Yes, I see it now. I see no protection in MS's CRITICAL_SECTION against this, either. It's basically (recursivity omitted) void lock( critical_section * p ) { if( atomic_increment(p->LockCount) != 0 ) { slow_lock_path( p ); } } void unlock( critical_section * p ) { if( atomic_decrement(p->LockCount) >= 0 ) { slow_unlock_path( p ); } } and it seems to me that slow_unlock_path happily accesses *p, in particular something called p->LockSemaphore (whether it is really a semaphore is another story).

Peter Dimov wrote: [...]
Yes, I see it now. I see no protection in MS's CRITICAL_SECTION against this, either. It's basically (recursivity omitted)
void lock( critical_section * p ) { if( atomic_increment(p->LockCount) != 0 ) { slow_lock_path( p ); } }
void unlock( critical_section * p ) { if( atomic_decrement(p->LockCount) >= 0 ) { slow_unlock_path( p ); } }
and it seems to me that slow_unlock_path happily accesses *p, in particular something called p->LockSemaphore (whether it is really a semaphore is another story).
Well, absent try/timed operations, that scheme is "posix safe", but slow once you have a bit of contention. The problem is that last "slow path" below. A: lock [lockcount == 0] B: lock [lockcount == 1, slow path] A: unlock [lockcount == 0, slow path/post sema] A: lock - [lockcount == 1, slow path] There's no need for A to go slow path here; the lock is free. regards, alexander.

Alexander Terekhov wrote:
Peter Dimov wrote: [...]
Yes, I see it now. I see no protection in MS's CRITICAL_SECTION against this, either. It's basically (recursivity omitted)
void lock( critical_section * p ) { if( atomic_increment(p->LockCount) != 0 ) { slow_lock_path( p ); } }
void unlock( critical_section * p ) { if( atomic_decrement(p->LockCount) >= 0 ) { slow_unlock_path( p ); } }
and it seems to me that slow_unlock_path happily accesses *p, in particular something called p->LockSemaphore (whether it is really a semaphore is another story).
Well, absent try/timed operations, that scheme is "posix safe", but slow once you have a bit of contention.
Here's TryEnterCriticalSection (-recursivity) for reference: bool try_lock( critical_section * p ) { return atomic_compare_exchange( p->LockCount, -1, 0 ) == 0; } CRITICAL_SECTIONs have no timed_lock. Your version can be made posix-safe at the expense of three atomic ops instead of one in unlock: void unlock() { atomic_increment( refs_ ); // as before if( atomic_decrement( refs_ ) == 0 ) { CloseHandle( event_ ); } } void destroy() { if( atomic_decrement( refs_ ) == 0 ) { CloseHandle( event_ ); } } That's too slow, I guess? But maybe you'd be able to think of a way to somehow combine the refs_ manipulation with the lock_ manipulation? The event pool solution would also work, but the problem there is that the pool would need its own synchronization. Unless it's an "interlocked slist".

Peter Dimov wrote: [...]
Here's TryEnterCriticalSection (-recursivity) for reference:
bool try_lock( critical_section * p ) { return atomic_compare_exchange( p->LockCount, -1, 0 ) == 0; }
The point of "swap based mutex" exercise was to avoid CAS because i386 doesn't have it. With CAS (and absent timedlock operation), it can be made safe and fast (better than MS "critical" stuff).
CRITICAL_SECTIONs have no timed_lock.
That's because timedlock() inevitable introduces a race.
Your version can be made posix-safe at the expense of three atomic ops instead of one in unlock:
void unlock() { atomic_increment( refs_ );
// as before
if( atomic_decrement( refs_ ) == 0 ) { CloseHandle( event_ ); } }
void destroy() { if( atomic_decrement( refs_ ) == 0 ) { CloseHandle( event_ ); } }
Yes. Or something along the lines of pthreads-win32 mutex (also two atomic ops... MS critical section lock/unlock ;-) ). I did it in order to support timedlock (it's used to solve the race with respect to timed out waiters, but the same scheme would work to synchronize the destruction of "swap based" thing -- simply lock/unlock that critsect in the destructor).
That's too slow, I guess?
Yes. AFAICS, the best solution (i386 does have atomic_bit_test_set): mutex_trylock: RETURN !atomic_bit_test_set_ddacq(&lock, 1) mutex_lock: WHILE atomic_bit_test_set_ddacq(&lock, 1) lock_queue_wait(&lock, 1) // wait if locked bit is set mutex_timedlock: WHILE atomic_bit_test_set_ddacq(&lock, 1) IF lock_queue_wait(&lock, 1, timeout) RETURN TRUE RETUN FALSE mutex_unlock: uintptr_t lock_queue; IF atomic_decrement_rel(lock_queue = &lock) THEN lock_queue_wake(lock_queue, 1) or something like that with kernel lock_queue interface. It would be safe, i386-friendly, memory efficient, and fast.
But maybe you'd be able to think of a way to somehow combine the refs_ manipulation with the lock_ manipulation?
It would need CAS and would still be "slow", I'm afraid.
The event pool solution would also work, but the problem there is that the pool would need its own synchronization.
But it can be slow (MS "native" critical or mutex or event/same would work just fine). You don't usually need bizillions lock create/destroy per nanosecond.
Unless it's an "interlocked slist".
Yep. regards, alexander.

Alexander Terekhov wrote:
Peter Dimov wrote: [...]
Here's TryEnterCriticalSection (-recursivity) for reference:
bool try_lock( critical_section * p ) { return atomic_compare_exchange( p->LockCount, -1, 0 ) == 0; }
The point of "swap based mutex" exercise was to avoid CAS because i386 doesn't have it. With CAS (and absent timedlock operation), it can be made safe and fast (better than MS "critical" stuff).
Why is i386 support important (for a Windows mutex implementation, I mean)?

Alexander Terekhov wrote:
Peter Dimov wrote: [...]
Why is i386 support important (for a Windows mutex implementation, I mean)?
I have PS/2 Model 80 (with 64 Megs) "tank" at home (in addition to A31p ThinkPad ;-) ).
He he. OK, if you had CAS (PS/2 Model 70 486) how would you do it?

Peter Dimov wrote: [...]
I have PS/2 Model 80 (with 64 Megs) "tank" at home (in addition to A31p ThinkPad ;-) ).
He he. OK, if you had CAS (PS/2 Model 70 486) how would you do it?
Ok, you owe me a "Blue Lightening" processor upgrade. ;-) How about void lock() throw() { while (int lock_status = m_lock_status.cas(0, 1, msync::ddacq)) if (lock_status < 0 || m_lock_status.cas(1,-1, msync::ddacq)) m_retry_event.wait(); } bool trylock() throw() { return !m_lock_status.cas(0, 1, msync::acq); } bool timedlock(absolute_timeout const & timeout) throw() { while (int lock_status = m_lock_status.cas(0, 1, msync::ddacq)) if (lock_status < 0 || m_lock_status.cas(1,-1, msync::ddacq)) if (!m_retry_event.timedwait(timeout)) m_retry_event.set(), return !m_lock_status.swap(1, msync::acq); return true; } void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); } <?> regards, alexander.

Alexander Terekhov wrote:
Peter Dimov wrote: [...]
I have PS/2 Model 80 (with 64 Megs) "tank" at home (in addition to A31p ThinkPad ;-) ).
He he. OK, if you had CAS (PS/2 Model 70 486) how would you do it?
Ok, you owe me a "Blue Lightening" processor upgrade. ;-) How about
void lock() throw() { while (int lock_status = m_lock_status.cas(0, 1, msync::ddacq)) if (lock_status < 0 || m_lock_status.cas(1,-1, msync::ddacq)) m_retry_event.wait();
Err. m_retry_event.wait(), m_retry_event.set();
}
bool trylock() throw() { return !m_lock_status.cas(0, 1, msync::acq); }
bool timedlock(absolute_timeout const & timeout) throw() { while (int lock_status = m_lock_status.cas(0, 1, msync::ddacq)) if (lock_status < 0 || m_lock_status.cas(1,-1, msync::ddacq)) if (!m_retry_event.timedwait(timeout)) m_retry_event.set(), return !m_lock_status.swap(1, msync::acq); return true; }
Forget it. timedlock() would make it "posix unsafe". lock_queue is the way to go. regards, alexander.

Alexander Terekhov wrote: [...]
Err. m_retry_event.wait(), m_retry_event.set();
I mean void lock() throw() { if (int lock_status = m_lock_status.cas(0, 1, msync::ddacq)) { do if (lock_status < 0 || m_lock_status.cas(1,-1, msync::ddacq)) m_retry_event.wait(); while (lock_status = m_lock_status.cas(0, 1, msync::ddacq)); m_retry_event.set(); } } bool trylock() throw() { return !m_lock_status.cas(0, 1, msync::ddacq); } void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); } Can't currently think of something better than that. regards, alexander.

Alexander Terekhov wrote:
Alexander Terekhov wrote: [...]
Err. m_retry_event.wait(), m_retry_event.set();
I mean
void lock() throw() { if (int lock_status = m_lock_status.cas(0, 1, msync::ddacq)) { do if (lock_status < 0 || m_lock_status.cas(1,-1, msync::ddacq)) m_retry_event.wait(); while (lock_status = m_lock_status.cas(0, 1, msync::ddacq)); m_retry_event.set(); } }
bool trylock() throw() { return !m_lock_status.cas(0, 1, msync::ddacq); }
void unlock() throw() { if (m_lock_status.swap(0, msync::rel) < 0) m_retry_event.set(); }
Bad day. It still can go boom. Forget it. lock_queue is the way to go. regards, alexander.

Dear Alexander and Peter, I would like to pay your attention to ACE Sychronization Wrapper Facades/see Chapter 10 C++ Network Programming, Volume 1, Douglas C. Schmidt, Stephen D. Huston/. ACE is a reliable open source library which is available at http://deuce.doc.wustl.edu/Download.html. It is a cross-platformed toolkit. Look at the mutex implementation that can work safely on the different platforms including Win32. It is more than 10 years experience of qualified developers. Use the patterns and experience. Regards Valery Salamakha --- Peter Dimov <pdimov@mmltd.net> wrote:
Alexander Terekhov wrote:
Peter Dimov wrote: [...]
Why is i386 support important (for a Windows mutex implementation, I mean)?
I have PS/2 Model 80 (with 64 Megs) "tank" at home (in addition to A31p ThinkPad ;-) ).
He he. OK, if you had CAS (PS/2 Model 70 486) how would you do it? _______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
__________________________________ Do you Yahoo!? Vote for the stars of Yahoo!'s next ad campaign! http://advision.webevents.yahoo.com/yahoo/votelifeengine/

val salamakha wrote: [... ACE ...]
It is more than 10 years experience of qualified developers. Use the patterns and experience.
"I would not recommend ACE library to a friend." (selm=opsaq8yiryti5cme%40my.ipcb.mos) http://groups.google.com/groups?selm=3D4EBE2F.97E29972%40web.de regards, alexander.

--- Alexander Terekhov <terekhov@web.de> wrote:
val salamakha wrote:
[... ACE ...]
It is more than 10 years experience of qualified developers. Use the patterns and experience.
"I would not recommend ACE library to a friend." (selm=opsaq8yiryti5cme%40my.ipcb.mos)
http://groups.google.com/groups?selm=3D4EBE2F.97E29972%40web.de
Thank you for links. I looked at the discussion concerning ancient ACE 5.1.12 4 years ago, corrected your code a little bit (create players in main(..))and started your samples tennis and tennisb. I see nothing wrong in work of the samples under ACE release 5.4.1, Win XP, Intel IV 3 Ghz. So I cannot share your opinion - life is dynamic. The ACE has a lot of patterns that could be used to solve some problems in programming. The patterns could be re-written using modern C/C++ language features and some of the patterns have been seen in new thread libraries. take care, Valery __________________________________ Do you Yahoo!? Take Yahoo! Mail with you! Get it on your mobile phone. http://mobile.yahoo.com/maildemo

val salamakha wrote: [...]
Thank you for links. I looked at the discussion concerning ancient ACE 5.1.12 4 years ago, corrected your code a little bit (create players in main(..))and started your samples tennis and tennisb. I see nothing wrong in work of the samples under ACE release 5.4.1, Win XP, Intel IV 3 Ghz.
Add a sleep() or two as suggested in the problem report. regards, alexander.

--- Alexander Terekhov <terekhov@web.de> wrote:
Add a sleep() or two as suggested in the problem report.
As mentioned in my previous msg, I did change implementation of the players (a ACE_Thread::spawn( playerA/*B*/)) in main(...) using modern implementation. Other parts of the code are unchanged. Sometimes problem exists in other place. Take care Valery __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

val salamakha wrote:
--- Alexander Terekhov <terekhov@web.de> wrote:
Add a sleep() or two as suggested in the problem report.
As mentioned in my previous msg, I did change implementation of the players (a ACE_Thread::spawn( playerA/*B*/)) in main(...) using modern implementation. Other parts of the code are unchanged.
I meant not the tennis stuff but the ACE's CV implementation. Well, I've just tried the latest and greatest ACE 5.4 on my MP IntelliStation. Same buggy behavior as in 2001. Looking at their CV impl, I see that they've not changed anything.
Sometimes problem exists in other place.
Now that's a progress. ;-) regards, alexander.

Alexander,
--- Alexander Terekhov <terekhov@web.de> wrote: I meant not the tennis stuff but the ACE's CV implementation.
Well, I've just tried the latest and greatest ACE 5.4 on my MP IntelliStation. Same buggy behavior as in 2001. Looking at their CV impl, I see that they've not changed anything.
If you want to discuss concerning ACE, please use PROBLEM_REPORT_FORM and send a report to ace-users forum or onto my private address - not to boost forum. It refines the problem and makes our discussion subject-oriented without useless emotions. Secondly, no need to re-invent a wheel again. If you want reliable signaling between threads on Windows, use the HANDLE-based Win32 mutex instead of playing around "CRITICAL SECTION" implementations in your wrapper class. Do not forget to mention about core of the Win32 pseudo-mutex class as it did in ACE. Take care Valery __________________________________________________ Do You Yahoo!? Tired of spam? Yahoo! Mail has the best spam protection around http://mail.yahoo.com

Peter Dimov wrote:
Alexander Terekhov wrote:
Peter Dimov wrote:
Is a CRITICAL_SECTION "posix-safe"? A HMUTEX?
I have no idea.
Hm. It'd be useful to know whether your swap-based mutex >= CRITICAL_SECTION (minus the recursivity, of course).
It'd be useful to have authoritative pseudo-code of their implementations (I mean all windows versions/service-packs/whatever). We could analyze it, then. Again, you could easily make swap-based mutex "posix safe" via indirection and reuse of impl. The race with respect to the state of auto-reset event and impl reuse is harmless. regards, alexander.

Michael Glassford wrote: [...]
if (m_lock_status.swap(1, msync::acq))
^^ ^^^^^^^^^^
msync:ddacq. http://groups.google.com/groups?selm=40CED460.1B7C3FAB%40web.de regards, alexander.
participants (4)
-
Alexander Terekhov
-
Michael Glassford
-
Peter Dimov
-
val salamakha