Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

experimental condvar w/ wait-morphing for windows...

5 views
Skip to first unread message

Chris Thomasson

unread,
Mar 8, 2008, 9:44:08 AM3/8/08
to
Here is the crude code-sketch which should compile:
_________________________________________________________________
#include <stdio.h>
#include <errno.h>
#include <limits.h>
#include <assert.h>
#define WIN32_LEAN_AND_MEAN
#define _WIN32_WINNT 0x0400
#include <windows.h>


typedef struct win_condvar_s win_condvar;

struct win_condvar_s {
LONG sig_req;
LONG sig_issue;
LONG sig_pend;
HANDLE mtx;
HANDLE wset_issue;
HANDLE wset_gate;
};


int win_condvar_sys_signal(
win_condvar* const _this,
LONG CONST count
) {
if (count) {
WaitForSingleObject(_this->mtx, INFINITE);
if (_this->sig_issue) {
_this->sig_pend += count;
} else {
LONG sig_prev;
_this->sig_issue = count;
ResetEvent(_this->wset_gate);
ReleaseSemaphore(_this->wset_issue, count, &sig_prev);
assert(! sig_prev);
}
ReleaseMutex(_this->mtx);
}
return 0;
}


int win_condvar_create(
win_condvar* const _this
) {
_this->sig_req = _this->sig_issue = _this->sig_pend = 0;
if (_this->wset_gate =
CreateEvent(NULL, TRUE, TRUE, NULL)) {
if (_this->wset_issue =
CreateSemaphore(NULL, 0, LONG_MAX, NULL)) {
if (_this->mtx =
CreateMutex(NULL, FALSE, NULL)) {
return 0;
}
CloseHandle(_this->wset_issue);
}
CloseHandle(_this->wset_gate);
}
return EAGAIN;
}


int win_condvar_destroy(
win_condvar* const _this
) {
if (! _this->sig_req &&
! _this->sig_issue &&
! _this->sig_pend) {
if (CloseHandle(_this->mtx) &&
CloseHandle(_this->wset_issue) &&
CloseHandle(_this->wset_gate)) {
return 0;
}
return EINVAL;
}
return EBUSY;
}


int win_condvar_signal(
win_condvar* const _this
) {
LONG cmp, cmp_tmp;
do {
cmp = _this->sig_req;
cmp_tmp = InterlockedCompareExchange
(&_this->sig_req, (cmp) ? cmp - 1 : 0, cmp);
} while (cmp != cmp_tmp);
if (cmp) {
return win_condvar_sys_signal(_this, 1);
}
return 0;
}


int win_condvar_broadcast(
win_condvar* const _this
) {
return win_condvar_sys_signal(_this,
InterlockedExchange(&_this->sig_req, 0));
}


int win_condvar_wait_mtx(
win_condvar* const _this,
HANDLE CONST umtx
) {
LONG sig_pend = 0;
HANDLE wset[3];
wset[0] = umtx;
wset[1] = _this->mtx;
wset[2] = _this->wset_issue;
WaitForSingleObject(_this->mtx, INFINITE);
InterlockedIncrement(&_this->sig_req);
ReleaseMutex(umtx);
SignalObjectAndWait(_this->mtx, _this->wset_gate,
INFINITE, FALSE);
WaitForMultipleObjects(3, wset, TRUE, INFINITE);
if (! (--_this->sig_issue)) {
sig_pend = _this->sig_pend;
_this->sig_pend = 0;
SetEvent(_this->wset_gate);
}
ReleaseMutex(_this->mtx);
if (sig_pend) {
win_condvar_sys_signal(_this, sig_pend);
}
return 0;
}


int win_condvar_wait_cs(
win_condvar* const _this,
LPCRITICAL_SECTION CONST umtx
) {
LONG sig_pend = 0;
HANDLE wset[2];
wset[0] = _this->mtx;
wset[1] = _this->wset_issue;
WaitForSingleObject(_this->mtx, INFINITE);
InterlockedIncrement(&_this->sig_req);
LeaveCriticalSection(umtx);
SignalObjectAndWait(_this->mtx, _this->wset_gate,
INFINITE, FALSE);
WaitForMultipleObjects(2, wset, TRUE, INFINITE);
if (! (--_this->sig_issue)) {
sig_pend = _this->sig_pend;
_this->sig_pend = 0;
SetEvent(_this->wset_gate);
}
ReleaseMutex(_this->mtx);
EnterCriticalSection(umtx);
if (sig_pend) {
win_condvar_sys_signal(_this, sig_pend);
}
return 0;
}
_________________________________________________________________


I have some questions that will be in another post...


--
Chris M. Thomasson
http://appcore.home.comcast.net

Chris Thomasson

unread,
Mar 8, 2008, 10:06:17 AM3/8/08
to
Okay, here is my main question... Given the following state:

HANDLE mtx = CreateMutex(NULL, FALSE, NULL);
HANDLE gate = CreateEvent(NULL, TRUE, TRUE, NULL);


And the following functions:

void epoch_begin() {
EB0: WaitForSingleObject(mtx, INFINITE);
EB1: ResetEvent(gate);
EB2: ReleaseMutex(mtx);
}

void epoch_request() {
ER0: WaitForSingleObject(mtx, INFINITE);
ER1: SignalObjectAndWait(mtx, gate, INFINITE, FALSE);
ER2: int x = 0; // simple placeholder
ER3: WaitForSingleObject(mtx, INFINITE);
ER4: SetEvent(gate);
ER5: ReleaseMutex(mtx);
}


under the following execution senerio:


ThreadA(ER0)
ThreadA(ER1)
ThreadA(ER2)
ThreadB(EB0)
ThreadB(EB1) - close gate
ThreadB(EB2)
ThreadC(ER0)
ThreadC(ER1) - waiting in 'SignalObjectAndWait()'
ThreadD(ER0)
ThreadD(ER1) - waiting in 'SignalObjectAndWait()'
ThreadA(ER3)
ThreadA(ER4) - open gate
ThreadA(ER5)


Will the 'SetEvent()' operation (e.g., ThreadA(ER4)) atomically wake 2
threads out of the 'SignalObjectAndWait()'? If not, then I think this is the
only time one could actually use 'PulseEvent()' safely:

void epoch_request() {
ER0: WaitForSingleObject(mtx, INFINITE);
ER1: SignalObjectAndWait(mtx, gate, INFINITE, FALSE);
ER2: int x = 0; // placeholder
ER3: WaitForSingleObject(mtx, INFINITE);
ER4: PulseEvent(gate);
ER5: SetEvent(gate);
ER6: ReleaseMutex(mtx);
}


Humm...

Chris Thomasson

unread,
Mar 10, 2008, 12:24:19 AM3/10/08
to

"Chris Thomasson" <cri...@comcast.net> wrote in message
news:mMqdnY3AJ57SO0_a...@comcast.com...

> Here is the crude code-sketch which should compile:
[...]

> int win_condvar_signal(
> win_condvar* const _this
> ) {
> LONG cmp, cmp_tmp;
> do {
> cmp = _this->sig_req;
> cmp_tmp = InterlockedCompareExchange
> (&_this->sig_req, (cmp) ? cmp - 1 : 0, cmp);
> } while (cmp != cmp_tmp);
> if (cmp) {
> return win_condvar_sys_signal(_this, 1);
> }
> return 0;
> }

The function above in not in error as-is, however it should really be
re-written as:

[...]

_________________________________________________________________


int win_condvar_signal(
win_condvar* const _this
) {

LONG cmp_tmp, cmp = _this->sig_req;
do {
if ((cmp_tmp = cmp) < 1) {
return 0;
}
cmp = InterlockedCompareExchange
(&_this->sig_req, cmp - 1, cmp);


} while (cmp != cmp_tmp);
if (cmp) {
return win_condvar_sys_signal(_this, 1);
}
return 0;
}

_________________________________________________________________


This way it takes advantage of the return value from the CAS! DUH! I really
should create a wrapper function/macro for this which provides an IBM-style
CAS... Something like:
_________________________________________________________________
inline BOOL
InterlockedCompareExchangeIBM(
LONG volatile* CONST pDest,
LONG CONST XChg,
LPLONG CONST pCmp
) {
LONG CONST Cmp = *pCmp;
LONG CONST CmpTmp =
InterlockedCompareExchange(pDest, XChg, Cmp);
if (Cmp == CmpTmp) {
return TRUE;
}
*pCmp = CmpTmp;
return FALSE;
}

/* I can now write the 'win_condvar_signal()' function like this: */


int
win_condvar_signal(
win_condvar* const _this
) {

LONG cmp = _this->sig_req;
do {
if (cmp < 1) { return 0; }
} while (! InterlockedCompareExchangeIBM(
(&_this->sig_req, cmp - 1, &cmp));


if (cmp) {
return win_condvar_sys_signal(_this, 1);
}
return 0;
}

_________________________________________________________________


IMHO, that is a more convenient CAS function...

Anthony Williams

unread,
Mar 26, 2008, 4:39:51 PM3/26/08
to
"Chris Thomasson" <cri...@comcast.net> writes:

No. There is no guarantee that SetEvent() will wake any currently-waiting
threads in any specific time-frame. In particular, if ResetEvent() gets called
before the waiting threads have been scheduled, then they won't wake (you've
just implemented PulseEvent manually).

> If not, then I think this is the
> only time one could actually use 'PulseEvent()' safely:
>
>
>
> void epoch_request() {
> ER0: WaitForSingleObject(mtx, INFINITE);
> ER1: SignalObjectAndWait(mtx, gate, INFINITE, FALSE);
> ER2: int x = 0; // placeholder
> ER3: WaitForSingleObject(mtx, INFINITE);
> ER4: PulseEvent(gate);
> ER5: SetEvent(gate);
> ER6: ReleaseMutex(mtx);
> }

PulseEvent won't help, for the same reasons it is always a bad idea: if the
thread isn't waiting when the event is pulsed (due to kernel APCs), then the
PulseEvent will have no effect.

Anthony
--
Anthony Williams | Just Software Solutions Ltd
Custom Software Development | http://www.justsoftwaresolutions.co.uk
Registered in England, Company Number 5478976.
Registered Office: 15 Carrallack Mews, St Just, Cornwall, TR19 7UL

Chris Thomasson

unread,
Mar 26, 2008, 6:34:57 PM3/26/08
to
"Anthony Williams" <anthon...@yahoo.com> wrote in message
news:3aqdrz...@yahoo.com...

> "Chris Thomasson" <cri...@comcast.net> writes:
>
>> Okay, here is my main question... Given the following state:
[...]

>> Will the 'SetEvent()' operation (e.g., ThreadA(ER4)) atomically wake 2
>> threads out of the 'SignalObjectAndWait()'?
>
> No. There is no guarantee that SetEvent() will wake any currently-waiting
> threads in any specific time-frame.

I was afraid of that. You would think that if there are N threads waiting on
a manual-reset, then N threads would wake when 'SetEvent()' was called.


> In particular, if ResetEvent() gets called
> before the waiting threads have been scheduled, then they won't wake
> (you've
> just implemented PulseEvent manually).

I was under the impression that 'SignalObjectAndWait()' would make sure that
race-condition would not happen. 'ResetEvent()' gets called inside the
critical-section and 'SignalObjectAndWait()' atomically unlocks the mutex
and waits on the manual-reset event. Well, this means that
'SignalObjectAndWait()' is basically useless in this specific context.

:^(

[...]

Anthony Williams

unread,
Mar 27, 2008, 4:28:01 AM3/27/08
to
"Chris Thomasson" <cri...@comcast.net> writes:

> "Anthony Williams" <anthon...@yahoo.com> wrote in message
> news:3aqdrz...@yahoo.com...
>> "Chris Thomasson" <cri...@comcast.net> writes:
>>
>>> Okay, here is my main question... Given the following state:
> [...]
>
>>> Will the 'SetEvent()' operation (e.g., ThreadA(ER4)) atomically wake 2
>>> threads out of the 'SignalObjectAndWait()'?
>>
>> No. There is no guarantee that SetEvent() will wake any currently-waiting
>> threads in any specific time-frame.
>
> I was afraid of that. You would think that if there are N threads waiting on
> a manual-reset, then N threads would wake when 'SetEvent()' was called.

They will, eventually. Unfortunately, not necessarily atomically with the
SetEvent() call.

>> In particular, if ResetEvent() gets called
>> before the waiting threads have been scheduled, then they won't wake (you've
>> just implemented PulseEvent manually).
>
> I was under the impression that 'SignalObjectAndWait()' would make sure that
> race-condition would not happen. 'ResetEvent()' gets called inside the
> critical-section and 'SignalObjectAndWait()' atomically unlocks the mutex
> and waits on the manual-reset event. Well, this means that
> 'SignalObjectAndWait()' is basically useless in this specific context.

SignalObjectAndWait means there is no gap between when the mutex is signalled,
and when the thread is waiting on the event. Unfortunately, as I understand
it, the Windows kernel may appropriate threads for APCs at any time, and if
the event being waited for is set and unset during that APC call (like
PulseEvent does), the waiting thread won't notice. Yes, that makes it useless
in this context.

Chris Thomasson

unread,
Mar 28, 2008, 6:17:14 PM3/28/08
to
"Anthony Williams" <anthon...@yahoo.com> wrote in message
news:y784r2...@yahoo.com...

Yup. This condvar is not going to work at all.

0 new messages