Thread-safe Queue on Win32

  • Follow


Hello.

Is this implementation of thread-safe queue correct? Does it have any
problem (correctness and performance)?

Note: hEvent is auto release event.

Push:
EnterCriticalSection(&cs);
queue.push(item);
SetEvent(hEvent);
LeaveCriticalSection(&cs);

Pop:
EnterCriticalSection(&cs);
WaitForSingleObject(hEvent, INFINITE);
item = queue.pop();
if (!queue.isEmpty())
    SetEvent(hEvent);
LeaveCriticalSection(&cs);
return item;

Thank you.
Roman Perepelitsa.

0
Reply Roman.Perepelitsa (3) 5/4/2007 1:02:08 PM

Roman.Perepelitsa@gmail.com schrieb:
> Is this implementation of thread-safe queue correct? Does it have any
> problem (correctness and performance)?

> EnterCriticalSection(&cs);
> WaitForSingleObject(hEvent, INFINITE);

It's obviously wrong. This will cause a deadlock.


Marcel
0
Reply ISO 5/4/2007 1:52:41 PM


On May 4, 5:52 pm, Marcel M=FCller <news.5.ma...@spamgourmet.com> wrote:
> Roman.Perepeli...@gmail.com schrieb:
>
> > Is this implementation of thread-safe queue correct? Does it have any
> > problem (correctness and performance)?
> > EnterCriticalSection(&cs);
> > WaitForSingleObject(hEvent, INFINITE);
>
> It's obviously wrong. This will cause a deadlock.
>
> Marcel

My bad. Should be:

Pop:
WaitForSingleObject(hEvent, INFINITE);
EnterCriticalSection(&cs);
item =3D queue.pop();
if (!queue.isEmpty())
    SetEvent(hEvent);
LeaveCriticalSection(&cs);
return item;

0
Reply Roman 5/4/2007 2:51:36 PM

On May 4, 7:51 am, Roman Perepelitsa <Roman.Perepeli...@gmail.com>
wrote:

> My bad. Should be:
>
> Pop:
> WaitForSingleObject(hEvent, INFINITE);
> EnterCriticalSection(&cs);
> item = queue.pop();
> if (!queue.isEmpty())
>     SetEvent(hEvent);
> LeaveCriticalSection(&cs);
> return item;

Suppose a thread calls 'WaitForSingleObject', finishes waiting, but
then before it can call 'pop', another thread grabs the entry from the
queue. This could result in your 'pop' method returning NULL.

It's also very inefficient. Here are a few things to think about:

1) Why wait for the event if the queue isn't empty? The wait is pure
waste.

2) Why set the event every time even if it was already set? The set is
pure waste.

Something to remember: EnterCriticalSection and LeaveCriticalSection
are fairly cheap. SetEvent and WaitForSingleObject are fairly
expensive.

DS

0
Reply David 5/4/2007 9:23:40 PM

Roman.Perepelitsa@gmail.com wrote:
> Hello.
> 
> Is this implementation of thread-safe queue correct? Does it have any
> problem (correctness and performance)?
> 
[...]

By way of a discussion on how to use win32 events.
http://groups.google.com/group/comp.programming.threads/msg/bdccc96487ee6cba?hl=en&


-- 
Joe Seigh

When you get lemons, you make lemonade.
When you get hardware, you make software. 
0
Reply Joe 5/4/2007 11:25:42 PM

Hi,

> Suppose a thread calls 'WaitForSingleObject', finishes waiting, but
> then before it can call 'pop', another thread grabs the entry from the
> queue. This could result in your 'pop' method returning NULL.

It's a of the Windows autoreset event that exactly one thread awakes on 
one SetEvent. Unfortunately it has no set count to let more than one 
thread pass following calls to WaitForSingleObject. I think the 
implementation correctly compensates for this.


> It's also very inefficient. Here are a few things to think about:
> 
> 1) Why wait for the event if the queue isn't empty? The wait is pure
> waste.

See above.

> 2) Why set the event every time even if it was already set? The set is
> pure waste.

That's true. The SetEvent is not required unless the queue was empty 
before queue.push.

> Something to remember: EnterCriticalSection and LeaveCriticalSection
> are fairly cheap. SetEvent and WaitForSingleObject are fairly
> expensive.

Of course, the performance could be better with a double check 
implementation of pop.


Marcel
0
Reply ISO 5/7/2007 8:14:22 AM

Thank you everyone for your answers. Finally I came up with this
implementation:

Push:
EnterCriticalSection(&cs);
bool wasEmpty = queue.isEmpty();
queue.push(item);
if (wasEmpty)
    SetEvent(hEvent);
LeaveCriticalSection(&cs);

Pop:
EnterCriticalSection(&cs);
while (queue.isEmpty())
{
    LeaveCriticalSection(&cs);
    WaitForSingleObject(hEvent, INFINITE);
    EnterCriticalSection(&cs);
}
item = queue.pop();
if (!queue.isEmpty())
    SetEvent(hEvent);
LeaveCriticalSection(&cs);
return item;

As far as I understand it's both correct and efficient in case of any
number of consumer and producer threads. I see only one possible
problem: This implementation is not "fair" (threads waiting for queue
to become nonempty can be awaken in any order), but I don't care about
it in my application.

Roman Perepelitsa.

0
Reply Roman 5/7/2007 10:12:59 AM

Roman Perepelitsa wrote:
> Thank you everyone for your answers. Finally I came up with this
> implementation:
> 
> Push:
> EnterCriticalSection(&cs);
> bool wasEmpty = queue.isEmpty();
> queue.push(item);
> if (wasEmpty)
>     SetEvent(hEvent);
> LeaveCriticalSection(&cs);
> 
> Pop:
> EnterCriticalSection(&cs);
> while (queue.isEmpty())
> {
>     LeaveCriticalSection(&cs);
>     WaitForSingleObject(hEvent, INFINITE);
>     EnterCriticalSection(&cs);
> }
> item = queue.pop();
> if (!queue.isEmpty())
>     SetEvent(hEvent);

You want
   if (queue.isEmpty())
     ResetEvent(hEvent);



> LeaveCriticalSection(&cs);
> return item;
> 
> As far as I understand it's both correct and efficient in case of any
> number of consumer and producer threads. I see only one possible
> problem: This implementation is not "fair" (threads waiting for queue
> to become nonempty can be awaken in any order), but I don't care about
> it in my application.
> 
> Roman Perepelitsa.
> 

You should look at semaphore based solutions also.  The logic is even simpler.
Semaphore is initialized to number of items on queue, zero for new empty queue.

pop:
   WaitForSingleObject(hSemaphore);
   EnterCriticalSection(&cs);
   item =  queue.pop();
   LeaveCriticalSection(&cs);
   return item;

push:
   EnterCriticalSection(&cs);
   queue.push(item);
   LeaveCriticalSection(&cs);
   ReleaseSemaphore(hSemaphore, 1, null);



-- 
Joe Seigh

When you get lemons, you make lemonade.
When you get hardware, you make software. 
0
Reply Joe 5/7/2007 10:30:06 AM

On May 7, 1:14 am, Marcel M=FCller <news.5.ma...@spamgourmet.com> wrote:

> That's true. The SetEvent is not required unless the queue was empty
> before queue.push.

Well, maybe not, depending on your implementation.

Suppose two threads are both blocked waiting for a message on the
queue. If two other threads each put a message on the queue, it's
important that both waiting threads be woken, even though one of the
two threads putting messages on the queue will not be putting a
message on an empty queue.

I have seen real-world implementations that had this problem. You can
wind up with the entire pool of threads save one blocked on rapidly-
filling queue, and only one thread is running because only one message
was put on an empty queue. Sadly, no new threads will be woken until
the one thread that is running finishes what it's doing -- which could
be a l-o-n-g time.

The sequence goes like this:

1) The queue is empty.

2) The whole pool of service threads is blocked on the queue.

3) Two threads put messages on the queue at roughly the same time (and
before any woken threads can run), only one puts a message on an empty
queue. Only one wakeup is commanded. The queue has two messages on it.

4) One thread is woken, pulls the first message, which is for a long
and complex task. The queue is still not empty, having one message
left.

5) No new threads will be woken because the queue is not empty and
will not empty because all threads from the pool (save the busy one)
are blocked. The whole pool and the queue are stalled until the only
long, complex task can be finished. (And if that task relies on
subtasks that involve this queue, you are permanently deadlocked. Yes,
I have actually seen this happen in real-world programs!)

There are several ways to fix this. One is to wake all threads instead
of one. Another is to wake a thread any time you put a message on a
queue. Yet another is to wake a thread if the count of waiting threads
is non-zero. And still another way is to wake a thread if you put a
message on a queue with fewer messages than servicing threads.

There are also queue architectures that are invulnerable to this race
condition for structural reasons or because they are built on top of
primitives that already work around it.

DS

0
Reply David 5/8/2007 12:11:51 AM

David Schwartz schrieb:
>> That's true. The SetEvent is not required unless the queue was empty
>> before queue.push.
> 
> Well, maybe not, depending on your implementation.
> 
> Suppose two threads are both blocked waiting for a message on the
> queue. If two other threads each put a message on the queue, it's
> important that both waiting threads be woken, even though one of the
> two threads putting messages on the queue will not be putting a
> message on an empty queue.

It will not take longer to awake the second thread, since every thread 
reading a message posts the event again unless the queue is empty. And 
this is done /before/ the processing of the message.

> There are several ways to fix this. One is to wake all threads instead
> of one. Another is to wake a thread any time you put a message on a
> queue.

The latter will not be sufficient with Win32 events, since they have no 
post count.

Posting the event if the queue was empty before push and if it is not 
empty after pop requires exactly one call to SetEvent per queue item.
Doing a double check at pop can reduce this further.


Marcel
0
Reply ISO 5/8/2007 8:18:42 AM

> You want
>    if (queue.isEmpty())
>      ResetEvent(hEvent);

I use auto-reset events. I don't think I need to call ResetEvent at
all.

Roman Perepelitsa.

0
Reply Roman 5/8/2007 1:34:23 PM

Roman Perepelitsa wrote:
>>You want
>>   if (queue.isEmpty())
>>     ResetEvent(hEvent);
> 
> 
> I use auto-reset events. I don't think I need to call ResetEvent at
> all.
> 



> Pop:
> EnterCriticalSection(&cs);
> while (queue.isEmpty())
> {
>     LeaveCriticalSection(&cs);

You have a race condition here.  The Event can
be auto reset before you even get a chance to
wait on it.


>     WaitForSingleObject(hEvent, INFINITE);
>     EnterCriticalSection(&cs);
> }
> item = queue.pop();
> if (!queue.isEmpty())
>     SetEvent(hEvent);
> LeaveCriticalSection(&cs);
> return item;

You can disagree but you need to prove that within that
window another thread cannot queue something on the
queue and auto reset the event before the about to wait
thread gets a chance to be Event's waitset.

-- 
Joe Seigh

When you get lemons, you make lemonade.
When you get hardware, you make software. 
0
Reply Joe 5/9/2007 12:20:33 AM

> > Pop:
> > EnterCriticalSection(&cs);
> > while (queue.isEmpty())
> > {
> >     LeaveCriticalSection(&cs);
>
> You have a race condition here.  The Event can
> be auto reset before you even get a chance to
> wait on it.
>
> >     WaitForSingleObject(hEvent, INFINITE);
> >     EnterCriticalSection(&cs);
> > }
> > item = queue.pop();
> > if (!queue.isEmpty())
> >     SetEvent(hEvent);
> > LeaveCriticalSection(&cs);
> > return item;
>
> You can disagree but you need to prove that within that
> window another thread cannot queue something on the
> queue and auto reset the event before the about to wait
> thread gets a chance to be Event's waitset.

Auto-reset events work another way. If you signal event and no threads
wait on event then it won't reset. It will be kept in signalling
state. Event resets automatically after some thread succesfully waits
for this event.

What you are saying is correct for PulseEvent. But I use SetEvent
here.

Roman Perepelitsa.

0
Reply Roman 5/11/2007 8:10:34 AM

"Roman Perepelitsa" <Roman.Perepelitsa@gmail.com> wrote in message 
news:1178871034.835801.98930@u30g2000hsc.googlegroups.com...
>> > Pop:
>> > EnterCriticalSection(&cs);
>> > while (queue.isEmpty())
>> > {
>> >     LeaveCriticalSection(&cs);
>>
>> You have a race condition here.

[...]


> Auto-reset events work another way. If you signal event and no threads
> wait on event then it won't reset. It will be kept in signalling
> state. Event resets automatically after some thread succesfully waits
> for this event.

Your waiters seem to have to go through an "undefined period" so to speak... 
They take no reference count, no eventcount, no specific waitset 
"identification card", no "nothing in particular", before they unlock 
themselves. If your thread can issue a signal that is suppose to make some 
other thread set some predicate that must be true in order for the issuer 
thread to make any sort of forward progresss well, you should learn about 
ping-pong signaling and the ability to use a single condition variable and 
the broadcast function to handle multiple predicate combinations. Joe knows 
99.9876% of the potential problems that may or may not be attributed to this 
sort of usage pattern..


..... And, well, what if the issuer (e.g., thread) makes the signal, and 
immediately and subsequently waits on the waitset to be an explicit state 
which means that some other group of thread(s) modifies the predicate in a 
specific and explicit fashion.?... POSIX condition variable rules clearly 
and explicitly state that a signal/broadcast MUST apply for the CURRENT 
waitset... PLEASE NOTE!, this does not include any soft of  SUBSEQUENT 
waitset indeed!! In order words a thread A which signals to a waiting thread 
pool of Threads B && c

:^)



0
Reply Chris 5/11/2007 8:50:53 AM

Roman Perepelitsa wrote:
>>>Pop:
>>>EnterCriticalSection(&cs);
>>>while (queue.isEmpty())
>>>{
>>>    LeaveCriticalSection(&cs);
>>
>>You have a race condition here.  The Event can
>>be auto reset before you even get a chance to
>>wait on it.
>>
>>
>>>    WaitForSingleObject(hEvent, INFINITE);
>>>    EnterCriticalSection(&cs);
>>>}
>>>item = queue.pop();
>>>if (!queue.isEmpty())
>>>    SetEvent(hEvent);
>>>LeaveCriticalSection(&cs);
>>>return item;
>>
>>You can disagree but you need to prove that within that
>>window another thread cannot queue something on the
>>queue and auto reset the event before the about to wait
>>thread gets a chance to be Event's waitset.
> 
> 
> Auto-reset events work another way. If you signal event and no threads
> wait on event then it won't reset. It will be kept in signalling
> state. Event resets automatically after some thread succesfully waits
> for this event.
> 
> What you are saying is correct for PulseEvent. But I use SetEvent
> here.
> 

You still have a race condition when there are mulitple threads
about to wait.  Only one will get signaled.



-- 
Joe Seigh

When you get lemons, you make lemonade.
When you get hardware, you make software. 
0
Reply Joe 5/11/2007 10:05:09 AM

[...]

> .... And, well, what if the issuer (e.g., thread) makes the signal, and 
> immediately and subsequently waits on the waitset to be an explicit state 
> which means that some other group of thread(s) modifies the predicate in a 
> specific and explicit fashion.?... POSIX condition variable rules clearly 
> and explicitly state that a signal/broadcast MUST apply for the CURRENT 
> waitset... PLEASE NOTE!, this does not include any soft of  SUBSEQUENT
                                                                             
             ^^^^^^^^^^^^^

Not 'soft'!

I mean 'sort'!

:^0


0
Reply Chris 5/11/2007 11:08:07 AM

> You still have a race condition when there are mulitple threads
> about to wait.  Only one will get signaled.

Notice this code:

item = queue.pop();
if (!queue.isEmpty())
    SetEvent(hEvent);
LeaveCriticalSection(&cs);

The first thread who awakes signals to other threads if there are more
elements in queue. No race condition.

Roman Perepelitsa.

0
Reply Roman 5/12/2007 6:45:52 AM

On 11 ���, 12:50, "Chris Thomasson" <cris...@comcast.net> wrote:
> "Roman Perepelitsa" <Roman.Perepeli...@gmail.com> wrote in message
>
> news:1178871034.835801.98930@u30g2000hsc.googlegroups.com...
>
> >> > Pop:
> >> > EnterCriticalSection(&cs);
> >> > while (queue.isEmpty())
> >> > {
> >> >     LeaveCriticalSection(&cs);
>
> >> You have a race condition here.
>
> [...]
>
> > Auto-reset events work another way. If you signal event and no threads
> > wait on event then it won't reset. It will be kept in signalling
> > state. Event resets automatically after some thread succesfully waits
> > for this event.
>
> Your waiters seem to have to go through an "undefined period" so to speak...
> They take no reference count, no eventcount, no specific waitset
> "identification card", no "nothing in particular", before they unlock
> themselves. If your thread can issue a signal that is suppose to make some
> other thread set some predicate that must be true in order for the issuer
> thread to make any sort of forward progresss well, you should learn about
> ping-pong signaling and the ability to use a single condition variable and
> the broadcast function to handle multiple predicate combinations. Joe knows
> 99.9876% of the potential problems that may or may not be attributed to this
> sort of usage pattern..

I didn't understand your answer? Do you think my implementation is
incorrect? In which case?

> .... And, well, what if the issuer (e.g., thread) makes the signal, and
> immediately and subsequently waits on the waitset to be an explicit state
> which means that some other group of thread(s) modifies the predicate in a
> specific and explicit fashion.?... POSIX condition variable rules clearly
> and explicitly state that a signal/broadcast MUST apply for the CURRENT
> waitset... PLEASE NOTE!, this does not include any soft of  SUBSEQUENT
> waitset indeed!! In order words a thread A which signals to a waiting thread
> pool of Threads B && c

This use case is too generic for me to understand it. Could you
provide simple scenario for my queue in which it fails?

Roman Perepelitsa.

0
Reply Roman 5/12/2007 6:48:52 AM

On May 11, 11:45 pm, Roman Perepelitsa <Roman.Perepeli...@gmail.com>
wrote:

> Notice this code:
>
> item = queue.pop();
> if (!queue.isEmpty())
>     SetEvent(hEvent);
> LeaveCriticalSection(&cs);
>
> The first thread who awakes signals to other threads if there are more
> elements in queue. No race condition.

That solves the problem, but is horrendously inefficient. You are
using one of the most expensive operations (SetEvent) in a situation
where it does nothing when you're under load. (Or, to put it another
way, you are writing code that becomes less efficient when under load,
which is very bad.)

DS

0
Reply David 5/12/2007 7:06:52 AM

18 Replies
569 Views

(page loaded in 0.183 seconds)

Similiar Articles:


















7/22/2012 9:40:17 PM


Reply: