Discussion:
Terminating a Worker Thread
(too old to reply)
Paul Braman
2004-09-10 13:45:16 UTC
Permalink
Consider this example program...

The main thread starts, creates a worker thread, waits for some
signal, then terminates the worker thread before rejoining it and
exiting.

In my "keep it simple" mindset, I figure that if I just pass the
thread a boolean flag and set the flag in the main thread when the
worker needs to exit, the worker can be pretty simple:

void * worker (void * data)
{
bool * done = (bool *)data;
while (! *done)
{
// ...
}
return NULL;
}

As long as it can periodically check the flag I should be set. I do
have some comments/questions about this as it relates to real MT
programming.

1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?

2) If I don't need to synchronize access to it, should I be declaring
it volatile since it will be manipulated randomly outside the scope of
the worker function?

3) Is there a better pattern for this kind of signaling?

Maybe this seems like a pretty basic problem, but after reading Herb
Sutter's article in C/C++ Users Journal about thread-safety sometimes
being "good enough", I'm going back and rethinking my ideas on things.

Thanks!


Paul Braman
Paul<dot>Braman<at>NielsenMedia<dot>com
Alexander Terekhov
2004-09-10 14:00:39 UTC
Permalink
Post by Paul Braman
Consider this example program...
The main thread starts, creates a worker thread, waits for some
signal, then terminates the worker thread before rejoining it and
exiting.
I'm not sure what you mean.
Post by Paul Braman
In my "keep it simple" mindset, I figure that if I just pass the
thread a boolean flag and set the flag in the main thread when the
void * worker (void * data)
{
bool * done = (bool *)data;
while (! *done)
{
// ...
}
return NULL;
}
As long as it can periodically check the flag I should be set. I do
have some comments/questions about this as it relates to real MT
programming.
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
"Applications shall ensure that access to any memory location by more
than one thread of control (threads or processes) is restricted such
that no thread of control can read or modify a memory location while
another thread of control may be modifying it. Such access is
restricted using functions that synchronize thread execution and
also synchronize memory with respect to other threads."
Post by Paul Braman
2) If I don't need to synchronize access to it, should I be declaring
it volatile since it will be manipulated randomly outside the scope of
the worker function?
42.
Post by Paul Braman
3) Is there a better pattern for this kind of signaling?
pthread_cancel() and/or something along the lines of

http://groups.google.com/groups?selm=3D64E675.DF19035%40web.de
("finish", I mean)

regards,
alexander.
Joe Seigh
2004-09-10 14:40:01 UTC
Permalink
Post by Alexander Terekhov
Post by Paul Braman
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
"Applications shall ensure that access to any memory location by more
than one thread of control (threads or processes) is restricted such
that no thread of control can read or modify a memory location while
another thread of control may be modifying it. Such access is
restricted using functions that synchronize thread execution and
also synchronize memory with respect to other threads."
That's really only a way of saying that Posix makes no guarantees if
you don't use pthread functions. But Posix makes not forward progress
guarantees anyway so it's a moot point. And not using Posix, especially
in areas not addressed by Posix, doesn't mean the solution is invalid.

Joe Seigh
Joe Seigh
2004-09-10 14:06:08 UTC
Permalink
Post by Paul Braman
Consider this example program...
The main thread starts, creates a worker thread, waits for some
signal, then terminates the worker thread before rejoining it and
exiting.
In my "keep it simple" mindset, I figure that if I just pass the
thread a boolean flag and set the flag in the main thread when the
void * worker (void * data)
{
bool * done = (bool *)data;
while (! *done)
{
// ...
}
return NULL;
}
As long as it can periodically check the flag I should be set. I do
have some comments/questions about this as it relates to real MT
programming.
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
No, as long as you aren't using the flag to imply the state or visiblity
of any other data. And if access of the boolean is not atomic then
in theory there may be an interval where the value of the flag is undefined.
But if you stop as soon as you see a change in the boolean as you
are doing then you should be ok.
Post by Paul Braman
2) If I don't need to synchronize access to it, should I be declaring
it volatile since it will be manipulated randomly outside the scope of
the worker function?
No, unless the loop is entirely compute bound, i.e. not calling any
external functions which would cause optimization to be dropped.
Post by Paul Braman
3) Is there a better pattern for this kind of signaling?
Some variation on the atomic<T> theme but it's not here yet.
Post by Paul Braman
Maybe this seems like a pretty basic problem, but after reading Herb
Sutter's article in C/C++ Users Journal about thread-safety sometimes
being "good enough", I'm going back and rethinking my ideas on things.
The C++ crowd usually evaluates things in the context of "thread-safe
as int" (really should be thread-safe as char[]). Depends on what
kind of assumptions you can make. If you can't depend on those
assumptions, it's not thread-safe.

Joe Seigh
Alexander Terekhov
2004-09-10 14:12:21 UTC
Permalink
Joe Seigh wrote:
[...]
Post by Joe Seigh
But if you stop as soon as you see a change in the boolean as you
are doing then you should be ok.
Except that under "lazy release consistency" you may never see a
change.

regards,
alexander.
Joe Seigh
2004-09-10 14:41:29 UTC
Permalink
Post by Alexander Terekhov
[...]
Post by Joe Seigh
But if you stop as soon as you see a change in the boolean as you
are doing then you should be ok.
Except that under "lazy release consistency" you may never see a
change.
Except with no forward progress guarantees in Posix you may never see a change.

Joe Seigh
Alexander Terekhov
2004-09-10 14:50:58 UTC
Permalink
Post by Joe Seigh
Post by Alexander Terekhov
[...]
Post by Joe Seigh
But if you stop as soon as you see a change in the boolean as you
are doing then you should be ok.
Except that under "lazy release consistency" you may never see a
change.
Except with no forward progress guarantees in Posix you may never see a change.
Even with forward progress guarantees in Posix you may never see a
change. In theory, at least.

regards,
alexander.
Joe Seigh
2004-09-10 14:58:48 UTC
Permalink
Post by Alexander Terekhov
Post by Joe Seigh
Post by Alexander Terekhov
[...]
Post by Joe Seigh
But if you stop as soon as you see a change in the boolean as you
are doing then you should be ok.
Except that under "lazy release consistency" you may never see a
change.
Except with no forward progress guarantees in Posix you may never see a change.
Even with forward progress guarantees in Posix you may never see a
change. In theory, at least.
Foward progress in the Posix standard? I agree. :)

Joe Seigh
Ronald Landheer-Cieslak
2004-09-10 18:46:28 UTC
Permalink
Post by Paul Braman
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
Not if you can change it atomically otherwise (i.e. using an
atomic_set() and an atomic_clear() for your boolean)
Post by Paul Braman
2) If I don't need to synchronize access to it, should I be declaring
it volatile since it will be manipulated randomly outside the scope of
the worker function?
IMHO, a good idea - if only to document this (and to prevent some
optimizations by the compiler)
Post by Paul Braman
Maybe this seems like a pretty basic problem, but after reading Herb
Sutter's article in C/C++ Users Journal about thread-safety sometimes
being "good enough", I'm going back and rethinking my ideas on things.
IMHO, you shouldn't count on Herb Sutter to define what is "good enough"
thread-safety. I've read the article too, and I agree with him on the
idea that if an in-use object is destroyed the results of using that
object should be undefined (which is really the only case he cites as
not-bugs, IIRC) and that a COW string should be only as thread-safe as a
"normal" string, but "threadsafe as int" is simply not thread-safe.

That said, he does use atomic increments and decrements for the ref
count, so he should be OK (but I haven't taken a very close look at his
code yet, so I won't vouch for it).

rlc
Kilgaard
2004-09-10 23:17:18 UTC
Permalink
Post by Ronald Landheer-Cieslak
Post by Paul Braman
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
Not if you can change it atomically otherwise (i.e. using an
atomic_set() and an atomic_clear() for your boolean)
Is there any real-world platform where a write to a naked bool variable
(which means setting the int value to 1) is not intrinsicly attomic anyway?

Sure things could be interesting if the bool is part of a bitfield inside a
struct ... but the OP is talking about using a "bool *" so that rules out
funky bitfields.
Post by Ronald Landheer-Cieslak
Post by Paul Braman
2) If I don't need to synchronize access to it, should I be declaring
it volatile since it will be manipulated randomly outside the scope of
the worker function?
IMHO, a good idea - if only to document this (and to prevent some
optimizations by the compiler)
As Joe alluded to, the situations where the compiler can legally optimize
this away is pretty much limited to "test" or "demonstration" code. In any
real worker function you will almost certainly be making function calls
which prevent the compiler for screwing you. That said, some compilers do
have "non-standard" optimization options that *do* enable this flag to be
optimized out.

For extra safety and documentation reasons, but the volatile in there ...
it's not going to hurt and may just safe you headaches later.
Alexander Terekhov
2004-09-11 15:21:19 UTC
Permalink
Kilgaard wrote:
[...]
Post by Kilgaard
As Joe alluded to, the situations where the compiler can legally optimize
this away is pretty much limited to "test" or "demonstration" code. In any
real worker function you will almost certainly be making function calls
which prevent the compiler for screwing you.
It doesn't "prevent the compiler for screwing you." XBD 4.10 says
that you SHALL synchronize access. Feel free to screw yourself, of
course.

regards,
alexander.
Ronald Landheer-Cieslak
2004-09-14 14:14:16 UTC
Permalink
Post by Kilgaard
Post by Ronald Landheer-Cieslak
Post by Paul Braman
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
Not if you can change it atomically otherwise (i.e. using an
atomic_set() and an atomic_clear() for your boolean)
Is there any real-world platform where a write to a naked bool variable
(which means setting the int value to 1) is not intrinsicly attomic anyway?
Yes. Actually, I guess you'd be more hard pressed to find a real-world
platform where it *is* intrinsicly atomic.
Post by Kilgaard
Sure things could be interesting if the bool is part of a bitfield inside a
struct ... but the OP is talking about using a "bool *" so that rules out
funky bitfields.
There are many issues setting an integer to a value - visibility,
atomicity, the compiler screwing you, the processor screwing you, etc.
Just think of all the parts between you and your integer - all of them
can screw you sideways..

rlc
Patrick TJ McPhee
2004-09-12 03:38:46 UTC
Permalink
In article <***@uni-berlin.de>,
Ronald Landheer-Cieslak <***@landheer.com> wrote:
% Paul Braman wrote:
% > 1) Since I'm really just using the boolean as a flag, do I *really*
% > need to synchronize access to it via a mutex?
% Not if you can change it atomically otherwise (i.e. using an
% atomic_set() and an atomic_clear() for your boolean)

Atomicity isn't the most important issue here. On a multi-processor,
the change to the boolean may not be seen in the worker thread until
some time after the change is made in the manager thread, due to
memory caching.

The question I have to ask is what do you hope to save by skipping
the mutex locks? Why not write it properly then break it only when you
need to milk it for speed.

% > 2) If I don't need to synchronize access to it, should I be declaring
% > it volatile since it will be manipulated randomly outside the scope of
% > the worker function?
% IMHO, a good idea - if only to document this (and to prevent some
% optimizations by the compiler)

This is frequently discussed here. If you use proper synchronisation,
the optimisations you're talking about will not take effect. volatile
will not help at all with atomicity of updates or the memory cache issue.
Use it for memory-mapped registers, but not for multi-threading.
--
Patrick TJ McPhee
East York Canada
***@interlog.com
Joe Seigh
2004-09-12 17:06:56 UTC
Permalink
Post by Patrick TJ McPhee
% > 1) Since I'm really just using the boolean as a flag, do I *really*
% > need to synchronize access to it via a mutex?
% Not if you can change it atomically otherwise (i.e. using an
% atomic_set() and an atomic_clear() for your boolean)
Atomicity isn't the most important issue here. On a multi-processor,
the change to the boolean may not be seen in the worker thread until
some time after the change is made in the manager thread, due to
memory caching.
There are no forward progress guarantees in Posix. So there is no guarantee
that you won't see an arbitrary delay either way, using or not using locks.
But on most if not all platforms, due to the fact that they all use strongly
coherent cache, not using a lock will be faster. In fact I could give
a strong argument that you are unlikely to see a non-strongly coherent cache
system meant to run Posix pthreads due to the extremely poor performance it
is likely to have.

Using locks does not make you see changes to memory faster. The Posix spec
says no such thing.

Joe Seigh
Ronald Landheer-Cieslak
2004-09-14 14:53:49 UTC
Permalink
Post by Patrick TJ McPhee
% > 1) Since I'm really just using the boolean as a flag, do I *really*
% > need to synchronize access to it via a mutex?
% Not if you can change it atomically otherwise (i.e. using an
% atomic_set() and an atomic_clear() for your boolean)
Atomicity isn't the most important issue here. On a multi-processor,
the change to the boolean may not be seen in the worker thread until
some time after the change is made in the manager thread, due to
memory caching.
Ehm.. Let me just clarify what I mean by "atomic": an atomic operation
on a memory operand is one that cannot be interrupted (either on the
processor itself or on any other processor that has access to the same
memory operand). This implies that the change is visible to other
processors immediatly too: as other processors can't interrupt the
change you *have to* work directly on the memory itself (writing through
all caches while you're at it).
Post by Patrick TJ McPhee
The question I have to ask is what do you hope to save by skipping
the mutex locks? Why not write it properly then break it only when you
need to milk it for speed.
The original question was whether you *really* need some kind of sync
primitive for the integer - the answer is "no", which is the answer I
gave. If you want to know whether you're really better off without a
sync primitive, the answer is "maybe".
Post by Patrick TJ McPhee
% > 2) If I don't need to synchronize access to it, should I be declaring
% > it volatile since it will be manipulated randomly outside the scope of
% > the worker function?
% IMHO, a good idea - if only to document this (and to prevent some
% optimizations by the compiler)
This is frequently discussed here. If you use proper synchronisation,
the optimisations you're talking about will not take effect. volatile
will not help at all with atomicity of updates or the memory cache issue.
Use it for memory-mapped registers, but not for multi-threading.
As long as the C (and C++) standard(s) is/are not updated to take
multi-threading into account, the best way we have to document that a
variable may change under our noses is the "volatile" keyword - even
though it wasn't meant for that and it doesn't work that way. It may not
help with atomicity, but that's what we have "proper synchronization"
for. It does help for self-documenting code.

rlc

SenderX
2004-09-11 20:43:54 UTC
Permalink
Post by Paul Braman
1) Since I'm really just using the boolean as a flag, do I *really*
need to synchronize access to it via a mutex?
If your flag has any state attached, you will need to sync the memory
between the producer and consumer. To use Alex's terms, the producer would
"sink" the loads and stores ( release ), and the consumer hoists them all up
( acquire ):

static volatile void *pFlag = 0;


Producer
----------------

/* Create the state */
void *pState = malloc( ... );

/* Set the flag */
InterlockedCompareExchangeRelease
( &pFlag,
pState,
0 );


Consumer
----------------

void *pState;

for ( pState = pFlag ;; pState = pFlag )
{
if ( pState && pState == pFlag )
{
if ( InterlockedCompareExchangeAcquire
( &pFlag,
0,
pState ) == pState )
{
/* Work with the state */

/* Free the state */
free( pState );
}
}

/* Do whatever */
}


This should be fast enough for a simple flag. There may be an indefinite
delay between the producer and consumer due to the unsynchronized check
before the cas.
Continue reading on narkive:
Loading...