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
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...
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...
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
>> 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" <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.
Yup. This condvar is not going to work at all.