AutoResetEvent vs. boolean to stop a thread
Asked Answered
G

5

13

I have an object in a worker thread, which I can instruct to stop running. I can implement this using a bool or an AutoResetEvent:

boolean:

private volatile bool _isRunning;

public void Run() {
    while (_isRunning)
    {
        doWork();
        Thread.Sleep(1000);
    }
}

AutoResetEvent:

private AutoResetEvent _stop;

public void Run() {
    do {
        doWork();
    } while (!_stop.WaitOne(1000));
}

The Stop() method would then set _isRunning to false, or call _stop.Set().

Apart from that the solution with AutoResetEvent may stop a little faster, is there any difference between these method? Is one "better" than the other?

Gardiner answered 14/8, 2012 at 13:22 Comment(2)
I would use a timer, rather than sleeping. To end it, you could then just stop the timer.Brick
I've added some observations to may answer, basically I don't think these snippets do what you want.Tamboura
C
13

C# volatile does not provide all the guaranties. It may still read stale data. Better to use underlying OS synchronisation mechanism as it provides much stronger guaranties.

All this in great depth discussed by Eric Lippert (really worth reading), here is short quote:

In C#, "volatile" means not only "make sure that the compiler and the jitter do not perform any code reordering or register caching optimizations on this variable". It also means "tell the processors to do whatever it is they need to do to ensure that I am reading the latest value, even if that means halting other processors and making them synchronize main memory with their caches".

Actually, that last bit is a lie. The true semantics of volatile reads and writes are considerably more complex than I've outlined here; in fact they do not actually guarantee that every processor stops what it is doing and updates caches to/from main memory. Rather, they provide weaker guarantees about how memory accesses before and after reads and writes may be observed to be ordered with respect to each other. Certain operations such as creating a new thread, entering a lock, or using one of the Interlocked family of methods introduce stronger guarantees about observation of ordering. If you want more details, read sections 3.10 and 10.5.3 of the C# 4.0 specification.

Frankly, I discourage you from ever making a volatile field. Volatile fields are a sign that you are doing something downright crazy: you're attempting to read and write the same value on two different threads without putting a lock in place. Locks guarantee that memory read or modified inside the lock is observed to be consistent, locks guarantee that only one thread accesses a given hunk of memory at a time, and so on.

Crushing answered 14/8, 2012 at 13:45 Comment(9)
The volatile works fine here. Any thread writing to one has all its memory updates pushed to main memory. Any thread reading one has its working memory refreshed from main. So a write to a volatile (and the data it references) will be seen by any read from that volatile. That said, the AutoResetEvent looks much better to me in this case.Mingle
@Mingle AFAIK on a multiprocessor systems there are only weak guaranties that all the processors will see an updated value.Crushing
@Crushing True, but strong enough in this case. Change an object referenced by non-volatile field obj1, then assign it to volatile field obj2. View object on another thread via obj2 and you will see the changes because you "read" obj2, forcing a full memory read. View it via obj1 and you might well see old values in the object, because there was no requirement to read from main memory. (I'm assuming obj1 references the same object in both threads.) There's other weak stuff, too, like the observed sequence of changes with multiple fields.Mingle
This all comes from ECMA-335, section II.12.6. If I'm misinterpreting it, someone let me know!Mingle
This is a good answer as I've just tracked down a problem with a thread spinning in a processing loop during a shutdown intermittently back to this exact problem of using a volatile and threads being spread across processorsDecile
Eric Lippert's quote still doesn't quite explain if it is OK to use bool to stop the thread. It only suggests to avoid volatile and use locks instead. If I put a lock { _keepRunning = false; } and _keepRunning is just a bool without volatile will this force processor to sync _keepRunning values for all threads? Or is it still a bad idea and I'd better use Events?Feil
@justamartin If you don't use volatile bool then the chances of the value being stale are quite high and your thread may not be stopped ever/in-a-defined-time/soon/immediately. When there are no guarantees, you cannot know the behaviour in advance. In other, words you may use volatile bool which will work in nearly all the cases. If you write critical software where you need to be 100% sure the value is updated immediately for all OS and CPU architectures - use events.Crushing
I'm sorry, I did not explain it more accurate. Eric Lippert discourages the use of volatile and says: "Locks guarantee that memory read or modified inside the lock is observed to be consistent". But the question is - does this mean that I can totally forget about volatile and instead use lock {} around value assignments, and I'll be good to go?Feil
You probably will be OK. But it's not that what the locks are designed for. They are heavy on performance because they provide some other execution guarantees like ordering, only one thread executes code at a time, the values are refreshed from raw memory and not some sort of cache, etc. In my opinion, using locks for reading immediate values from memory is not efficient, but will do what you are after. A better approach will be to use volatiles (with weak guarantees) or events (strong guarantees) because this is what they have been designed forCrushing
G
7

Volatile isn't good enough, but practically it will always work because the operating system scheduler will always take a lock eventually. And will work well on a core with a strong memory model, like x86 which burns a lot of juice to keep caches synchronized between cores.

So what really only matters is how quickly a thread will respond to the stop request. It is easy to measure, just start a Stopwatch in the control thread and record the time after the while loop in the worker thread. The results I measured from repeating taking 1000 samples and taking the average, repeated 10 times:

volatile bool, x86:         550 nanoseconds
volatile bool, x64:         550 nanoseconds
ManualResetEvent, x86:     2270 nanoseconds
ManualResetEvent, x64:     2250 nanoseconds
AutoResetEvent, x86:       2500 nanoseconds
AutoResetEvent, x64:       2100 nanoseconds
ManualResetEventSlim, x86:  650 nanoseconds
ManualResetEventSlim, x64:  630 nanoseconds

Beware that the results for volatile bool are very unlikely to look that well on a processor with a weak memory model, like ARM or Itanium. I don't have one to test.

Clearly it looks like you want to favor ManualResetEventSlim, giving good perf and a guarantee.

One note with these results, they were measured with the worker thread running a hot loop, constantly testing the stop condition and not doing any other work. That's not exactly a good match with real code, a thread won't typically check the stop condition that often. Which makes the differences between these techniques largely inconsequential.

Galangal answered 15/8, 2012 at 1:17 Comment(0)
W
3

IMHO the AutoResetEvent is better, because you can't forget the crucial volatile keyword in this case.

Weatherby answered 14/8, 2012 at 13:25 Comment(0)
T
1

Before using the volatile keyword, you should read this and, I guess, when researching multi-threading you could read the whole http://www.albahari.com/threading/ article.

It explains the subtleties of the volatile keyword and why its behaviour can be unexpected.


You'll note, that when using volatile, reads and writes can get reordered which could result in an extra iteration in closely concurrent situations. In this case you may have to wait for around one extra second.


After looking, I don't think your code works for several reasons,

The "boolean:" snippet always sleeps for approximately a second, probably not what you want.

The "AutoResetEvent:" snippet doesen't instantiate _stop and, will always run doWork() at least once.

Tamboura answered 14/8, 2012 at 13:36 Comment(2)
I forgot a ! on _stop.WaitOne(). These examples are a bit simplified, but basically I want to run doWork() periodically. DoWork() would run until it has run out of work, after which it has to wait a while for new work to accumulate.Gardiner
@Sjoerd, I edited my last part. The caution around volatile is still pertinent.Tamboura
P
0

It depends if the above snippet is all you are doing or not. The AutoReset event, as its name suggests, gets reset after the WaitOne is passed. This means that you could then use it again straightaway, rather than with the bool, having to set it back to true.

Pomeranian answered 14/8, 2012 at 13:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.