Re: thread-safety

Tech-Archive recommends: Repair Windows Errors & Optimize Windows Performance



Thank you very much for such a detailed response. Will try and answer
your comments below.

On 6 Jan, 10:34, "Peter Duniho" <NpOeStPe...@xxxxxxxxxxxxxxxx> wrote:
On Tue, 06 Jan 2009 00:54:30 -0700, RedLars <Liverpool1...@xxxxxxxxx>  
wrote:

[...]
This setup has worked fine so far. Now I need to implement a stop()
method which brings up a few concerns regarding thread safety.

Just for the record, there's nothing in the code you posted that suggests  
it's itself thread-safe.  We might assume it is, but that would depend on  
the implementation of your Queue class (obviously not one of the .NET  
queue classes).

A few comments
* A job.Execute() beeing executed must be completed before the thread
is stopped.
* A job.Execute() operation might take upto a few seconds to complete
(Asynch operation).
* Any unprocessed tasks in the queue, or created timers shall not be
deleted by Stop() method.
* The Queue (queue variable) has a Wakeup() method that stops the
queue.Next() from blocking and returns null;

To prevent stop() occuring during execution of a job a lock(..) was
introduced.

What do you mean "prevent stop() occurring during execution of a job"?  As  
you've already noted, until your job.Execute() method comes back, your  
consumer thread isn't going to do anything else, including stopping.  
What's in the "stop()" method that you need to not occur until after  
job.Execute() returns?

Apoligize for poor wording. What I'd like to achieve is for the Stop()
method to block until the consumer-thread actually has stopped
processing tasks. So if the job.Execute() is half-way into processing
a task, then I would like Stop to block until that task has been
completed before returning to calleer.


private object threadLocker = new object();
private void Thread_Main() {
   while (!TerminatedThread) {
           lock (threadLocker) {
                   IJob job = queue.Next() as IJob;
                   if (job != null) job.Execute();
           }
   }
}

What other code locks on "threadLocker"?  Without knowing that, there's no  
useful comments to be made about the above.

To actually stop the while loop from running I thought about adding
Monitor.Wait(...) to the loop.

How would that be different from your queue implementation which already  
has a "release on new item or wake-up" semantic built right in?  (For that  
matter, I'm surprised at the implication that the queue implementation  
doesn't already use Monitor.Wait() or something similar).


The semantics I've worked by so far is Stop() means to pause-
processing-of-the-queue and not stop-adding-tasks-to-queue.

Say as an example I ran the following code;
ThreadClass.AddJob( job1 );
ThreadClass.AddJob( job2 );
ThreadClass.AddJob( job3 );
ThreadClass.Stop();
Assuming each job requires 1 second to complete, I would expect the
first job to be completed before the thread has been stopped
(obviously depends on current state of ThreadClass and timing).

So the way I see this, the while loop needs a "sleep" state, it cannot
rely on the queue.Next() to block because it might still contain data.

The Queue implementation uses ManualResetEvent.



But to be able to add such mechanism I
would need a state flag to distinguish between running and not running
(stopped) mode. So I thought about adding private enum State
{ Created, Started, Stopped };

private volatile State state;
private ManualResetEvent StopThreadResetEvent = new ManualResetEvent
(false);
private void Thread_Main()
{
   while (!TerminatedThread) {
       if (state == State.Started) {
                   lock (threadLocker) {
                           IJob job = queue.Next() as IJob;
                           if (job != null) job.Execute();
                   }
           }
           else if (state == State.Stopped)
           {
                   Monitor.Wait(StopThreadResetEvent);
           }
   }

That code won't work.  The thread has to have taken the lock on  
"StopThreadResetEvent" before it can call Monitor.Wait() using that  
object.  That said...

Point taken.


Who sets "StopThreadResetEvent"?  Why do you need that?  The Monitor class  
certainly doesn't.  It's odd for you to be passing a WaitHandle of any  
sort to the Monitor class; if you've got a WaitHandle, you would normally  
just wait on _that_, rather than using the WaitHandle simply as a  
synchronization object for Monitor.


Will look into that.

So would it be enough to implement the stop method like this (?);
private void Stop() {
    if (state == State.Started) {
           state = State.Stopped;
           queue.Wakeup();
   }
}

This Stop() method doesn't interact with any of the synchronization you  
put into your consumer thread.  If you meant for the synchronization to  
somehow affect the way that the Stop() method works, you've failed in that  
goal.

The goal was to set the while loop into a non-processing-tasks-state.
Then the Start() method could interact with synchronization in
consumer thread.


From what I can understand this would not guarantee that Thread_Main
is at Monitor.Wait(...) when Stop() returns.
Could that be a problem?

It would be a problem to call Monitor.Pulse() if no other thread is  
waiting at a call to Monitor.Wait(), because then the waiting thread would  
miss the Pulse().  However, typically the waiting thread would always be  
either in the lock or waiting, ensuring that no other thread _can_ call  
Monitor.Pulse() until the waiting thread is in fact waiting.

I guess I could add
lock (threadLocker) { /* no code */ }
after Wakeup() as a form of waiting until Thread_Main has completed
its current task.

I don't see what the point of that would be.  It would only block if some  
other thread already had the "threadLocker" lock, but a) there's nothing  
in the code you posted that would indicate your consumer thread would be  
doing anything useful with that lock after you lock on it, and b) if that  
thread _were_ going to do something useful with that lock, you would do  
that useful thing _inside_ the locked block of code (instead of "no code").


The point i was trying to make was, if the consumer thread is
currently processing a task when the Stop() is executed and Stop()
only changed the state, then method would return prematurely (before
the consumer thread actually was in a Stopped \ pause state).

I would also need to change the Start() method a bit;
public void Start() {
    if (state == State.Created)
           thread = new Thread(new ThreadStart(this.Thread_Main));
           thread.Start();
   }
   else if (state == State.Stopped)
   {
           state = State.Started
           StopThreadResetEvent.Set();
   }
}

First, you need synchronization around the code that affects the "state"  
variable.  Second, here you actually use the ManualResetEvent in the usual  
way (by setting it), but setting it will have no effect at all on the call  
to Monitor.Wait() in your consumer thread.

You seem to be trying to mix and match different thread synchronization  
techniques.  That's not going to work.

What if Stop() and Start() are executed within a very short timeframe
- could that cause a problem with the current code?

All due respect, the code you posted already has so many problems that a  
theoretical race condition is the least of your worries.  :)

That said, if you implement everything correctly, there should be no  
problem at all with the timing of the calls to Stop() and Start().

I get the feeling I'm missing something when it comes handling state
\state-changes in a multi-threaded environment.

Appreicate any comments.

There's nothing particularly difficult about dealing with "state-changes",  
but you do need to use the thread synchronization mechanisms correctly.

I can't offer much else other than the above comments, without a  
concise-but-complete code sample that accurately illustrates your  
situation.  The fundamental idea seems sound (you want a consumer thread  
that you can essentially pause, and which also already has a "wait"  
semantic in it to deal with queue insertions), but you seem to be  
over-complicating things.

Part of the problem seems to me to be that your queue implementation  
itself has some synchronization mechanism built into it, which means you  
don't have good control over the way the Next() method operates.  Even so,  
since your queue does have the "wake-up" method, it seems to me that you  
should be able to take advantage of that rather than adding a new  
WaitHandle into the mix (your ManualResetEvent object).

And if you for some reason decide that you _must_ use the WaitHandle  
semantics in addition to the queue's existing semantics, you obviously  
should be sure to use it correctly.  When using a WaitHandle, the Monitor  
class is superfluous and won't work the way you seem to be trying to use  
it.

Assuming the queue implementation really does already have correct thread  
synchronization built into it, then I suspect you might not need to add  
any synchronization at all to your consumer thread.  You should be able to  
make your state variable "volatile", and then just use the synchronization  
that's implicit in your queue.  If you find that you must do the "check  
value then set" with your state variable, then you'll have to synchronize  
that (e.g. with a "lock" statement), so that multiple threads don't wind  
up doubling up on operations or failing to complete an intended operation..

Pete

.



Relevant Pages

  • Re: thread-safety
    ... but that would depend on the implementation of your Queue class. ... lock { ... It's odd for you to be passing a WaitHandle of any sort to the Monitor class; if you've got a WaitHandle, you would normally just wait on _that_, rather than using the WaitHandle simply as a synchronization object for Monitor. ... It would be a problem to call Monitor.Pulseif no other thread is waiting at a call to Monitor.Wait, because then the waiting thread would miss the Pulse. ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Storing Thread events in a Vector
    ... can't figure out how to get the Consumer thread to see the elements ... ProducerThread producer = new ProducerThread; ... String outputTime = format.format; ... You don't have any synchronization between your threads. ...
    (comp.lang.java.help)
  • Re: Network Stack Locking
    ... coallescing of context switches to process multiple packets. ... therefore effective coallescing of synchronization. ... for example, if your stack trace in the ... TCP code only goes up to the queue receive primitive, ...
    (freebsd-arch)
  • Re: Queue.Dequeue() causes exception "Object reference not set to an instance of an object."
    ... I wrote a simple test app which using the Queue object. ... Dequeue() again I don't get the exception. ... This means that the producer thread could be in the process of changing the underlying data structures at the same time your consumer thread tries to dequeue an item. ...
    (microsoft.public.dotnet.languages.csharp)
  • Re: Queue.Dequeue() causes exception "Object reference not set to an instance of an object."
    ... I'm using almost the same approach as alexia in using Queue. ... there is a very high likelihood of the receiving thread not getting a chance to execute, adding something to the queue, before the original thread gets into the cSocket.Processmethod and tries to dequeue an item. ... Due to the lack of synchronization, you also have other potential threading issues. ...
    (microsoft.public.dotnet.languages.csharp)