Concurrent Containers

S

Scott Meyers

Misleading names don't help either.

Of course not, but that's again orthogonal. My understanding is that more than
a few physicists and mathematicians have stared in dumbfounded astonishment at
what C++ programmers call a "vector," but that doesn't change the fact that lots
of people -- often including those same physicists and mathematicians, once they
get past the name -- find it useful.
Good, the point is that the interface must not have a single function that can
be used that way, and be unsafe.

Oh, don't be silly. Of course it can. Or would you ban operator[] from arrays
and vectors, because people can use invalid indices?

If it makes you feel any better, TBB has the convention of prefixing
concurrency-unsafe operations in their concurrent containers with "unsafe,"
e.g., "unsafe_erase" and "unsafe_bucket_count" on concurrent_unordered_map).
Also, if the component covers my MT work only halfway -- I have to use explicit
synch incantations for at least some operations, then the gain on the other
operations is moot.

Since when do you reject a library component because you have to write some
custom code to work with it? Do you write your own containers from scratch
because the STL ones don't have exactly the interface you need for your
applications, or do you write some custom code with the interface you want and
use STL under the hood to implement that interface?
Also, if the object does locking that is not evidently showing its spots, it is
easy to create deadlock situations, you only need another mutex, say from
another such hash_map.

From TBB's documentation on concurrent_unordered_map:
The interface has no visible locking. It may hold locks internally,
but never while calling user defined code.

I think your objections boil down to "it's possible for people to do a bad job
with the design and implementation of a concurrent data structure." They might
choose bad names, they might design error-prone APIs, they might not think about
deadlock. You're right, they might. But if the possibility of bad design and
implementation choices were the only consideration, nobody would be allowed to
program anything in C++. I think that if you were to look at the TBB/PPL
concurrent container APIs instead of simply imagining what they might be, you'd
see that those APIs were designed with care.

Scott
 
A

Alexander Terekhov

Think of thread-safety akin to C++ exception-safety with three levels:

thread_safety::unsafe means that even "const" copying and all other
const and non-const operations on a data structure and its copies
shall be synchronized; otherwise, the behavior is undefined. Read-only
(very strong/real const) stuff can be done concurrently.

thread_safety::basic means that all "const" operations can be done
concurrently, but mutations shall be synchronized with respect to
"const" operations and other mutations; otherwise, the behavior is
undefined.

thread_safety::strong means "do-whatever-you-want-and-don't-
care-about-synchronization" (i.e. you can also mutate instances
without any synchronization because it's all fully synchronized
internally).

regards,
alexander.
 
T

tni

Since when do you reject a library component because you have to write
some custom code to work with it? Do you write your own containers from
scratch because the STL ones don't have exactly the interface you need
for your applications, or do you write some custom code with the
interface you want and use STL under the hood to implement that interface?


From TBB's documentation on concurrent_unordered_map:

That's exactly the problem. If you need the locking to cover multiple
operations (e.g. two inserts, because the container content is otherwise
invalid), you need to introduce an external lock. But now every other
operation on the container needs to use that external lock as well. The
internal locking has become useless.
 
S

Scott Meyers

That's exactly the problem. If you need the locking to cover multiple operations
(e.g. two inserts, because the container content is otherwise invalid), you need
to introduce an external lock. But now every other operation on the container
needs to use that external lock as well. The internal locking has become useless.

I see your point.

I speculate that perhaps the expected usage model is phased. E.g., in Phase 1,
all threads perform only operations that are concurrency-safe, hence no external
locking is needed. For example, many threads read web logs and perform
concurrent insertions into a shared map of, e.g., client IP addresses and
timestamps. In Phase 2, a single thread traverses the shared data structure to
trim out unneeded entries, e.g., those that are older than a certain time.
Because there is only one thread, again, no external locking is needed. We then
return to phase 1.

But that's speculation. I've been trying to get information from Intel or
Microsoft about the use cases for which their concurrent data structures are
designed, but so far I've had no success. I'll keep poking.

Scott
 
B

Balog Pal

Scott Meyers said:
Good, the point is that the interface must not have a single function
that can
be used that way, and be unsafe.

Oh, don't be silly. Of course it can. Or would you ban operator[] from
arrays and vectors, because people can use invalid indices?

No. I don't think those play in the same category. op[] usage indeed has
the chance to pass a bad index, and I certainly saw many situations to find,
review or even create such a bug. But I'm yet to meet a programmer who is
not aware that the obligation of keeping the passed thing in bounds is
there. Or who is confused about the fact that whatever expression
evaluates between [] shall give a proper number.

Race conditions are subtle, programmers who just "see" them are rare and
precious, and too many do not learn to feel such errors even after
accidents, reviews, whatever.
If it makes you feel any better, TBB has the convention of prefixing
concurrency-unsafe operations in their concurrent containers with
"unsafe," e.g., "unsafe_erase" and "unsafe_bucket_count" on
concurrent_unordered_map).

They do? Yeah, that makes me absolutely feel feel better. If you
sometimes write guidelines that touch threading and interfaces, a written
advice to follow similar practice would make my day for sure.
Since when do you reject a library component because you have to write
some custom code to work with it? Do you write your own containers from
scratch because the STL ones don't have exactly the interface you need for
your applications, or do you write some custom code with the interface you
want and use STL under the hood to implement that interface?

Let's not go to extreme relativization (or what is it called ;-). Writing a
container (or most library stuff) involves many lines of code.

Inserting critical sections where they are needed is just a few lines.
Working with traditional objects in the MT environment is not a big deal, if
your overall design is sound, and you did identify the points of sharing.

Using a magic class that just does to good thing is cool. But replacing the
traditional way with some half-halping thing will hurt more than help,
believe me.
From TBB's documentation on concurrent_unordered_map:

That is at least a clear. :) (I recall a tough situation from practice, we
created an observer framework, that called registered notification functions
on certain events -- and holding a lock on the collection we iterated on was
bad, but releasing it also created a bag of possible problems.)

In C++ it is easy to overlook 'user code' usage, especially with a template
library.
I think your objections boil down to "it's possible for people to do a bad
job with the design and implementation of a concurrent data structure."
They might choose bad names, they might design error-prone APIs, they
might not think about deadlock.

No, this is not my main problem. It's more around the communication gap
that involves this whole topic. Let's just assume that people creating
those libs know MT in and out, and are aware of all the problem cases.
And even document them precisely. What kind of knowledge you expect from
the audience programmers, to have safe results?

Let's look a parallel. Who can write a string class? Sure, anyone. But
Joe Average's string class will likely have some leaks if exceptions happen,
maybe even other problems. While a good programmer can write it to be all
correct and sound.
Then once the latter guy published his class, that Joe can use the class all
year without being able to leak memory. Probably even without careful study
of the manual.

With MT I see that the user shall know as much as the pro just to evaluate
which component to pick from the library, and how to use it. JA will be
lost with basic elements -- and about as lost with more specialised lib.

Yet, as I tried to state earlier, I do not onject writing those libs. They
are by all means welcome. I just did not see too much payoff from them,
or even the possibility. Because my similar-looking use cases turned out
to have differences that flushed chance of good generalization.

You're right, they might. But if the possibility of bad design and
implementation choices were the only consideration, nobody would be
allowed to program anything in C++.

Well, I don't want to offend anyone, but I actually believe that programming
is not a sport for everyone, and programming in UB-infested languages like C
or C++ is even so. Practicing as a doctor, lawyer, architect and many
other disciplines are tied to licence and quality control, that the software
field painfully lacks -- and that cause a big deal of losses in life, limb
and property. but that leads far offtopic ;-)
I think that if you were to look at the TBB/PPL concurrent container APIs
instead of simply imagining what they might be, you'd see that those APIs
were designed with care.

Sure, I will, and thanks to bring them to my attention.
Still my real problem is not with the libraries, but their users. ;)
 
J

Joshua Maurice

IMO a fast inter-thread queue would be best fitted to become a  
standardized and reusable component. I have mostly needed blocking
queues, so I had a quick look on the TBB blocking queue
(concurrent_bounded_queue). For fetching data there are pop() and try_pop
() members, one blocking and other non-blocking. The first deficiency is
that the blocking pop() lacks a timeout parameter, so timed wait is not
possible. One could argue that relying on a timed wait is not the best
design, but the point is that if I want to have it I just cannot use this
data structure. The second and more serious problem is that if I want to
abort the waiting thread, there are no means for cancelling the blocking
wait and throw an exception (thread cancellation has not been
standardized AFAIK and TBB documentation for this class does not mention
cancellation). I could just post a cancellation message to the queue, but
the queue might be filled up with other messages so I would still need a
separate global flag to be checked separately by the waiter thread (and
protected by another totally unneeded mutex).

I'm grappling with the same problem now. In short, all thread
cancellation in C++ is cooperative, and cooperative thread
cancellation is a bitch. Even if you had access to all of the source
code of your project, correctly identifying all spots which block to
implement cooperative thread cancellation seems daunting.

I haven't had that much experience in this area, but I've dabbled a
bit. I think that your idea of a separate global-like (or global to a
specific job) stop flag is the only reasonable way out. However, it
does not need a mutex under C++0x. You can use std atomic flag with
memory_order_seq_cst and achieve the same results. Optionally, you
could use std::memory_order_relaxed, though I'm not sure if this would
be good practice as the update to other threads may be arbitrarily
delayed. (However, the C++0x draft strongly suggests to implementers
to make sure relaxed writes become visible "quickly".)

The annoying part is that you may want to make all waits into timed
waits to poll that stop flag. I am toying around with ideas in my head
to avoid timed waits, but I'm unsure at the moment if it would be
practical.
 
S

Scott Meyers

The annoying part is that you may want to make all waits into timed
waits to poll that stop flag. I am toying around with ideas in my head
to avoid timed waits, but I'm unsure at the moment if it would be
practical.

Please let us know if you come up with something.

Scott
 
J

Joshua Maurice

Please let us know if you come up with something.

I was thinking of somehow adding abstractions so that stack unwinding
and RAII could be used to "wake up" any potential threads blocked on
yourself. The problem is that it requires programmer care. Any missed
spot could result in a thread blocked forever. I'm not sure what these
abstractions would look like. Overall, the entire idea of thread and
job cancellation just looks like a huge mess.
 
J

Joshua Maurice

I was thinking of somehow adding abstractions so that stack unwinding
and RAII could be used to "wake up" any potential threads blocked on
yourself. The problem is that it requires programmer care. Any missed
spot could result in a thread blocked forever. I'm not sure what these
abstractions would look like. Overall, the entire idea of thread and
job cancellation just looks like a huge mess.

Ok. Where to start. Let me just type out what's going through my head
here, and maybe people can add.

The basic idea is that we have some threads doing work as part of a
"job". An external thread has access to the job object, and it can
issue a stop request on the job. The threads doing the job need to
periodically check some global-like stop flag, and if it's set, then
they need to do the stop - leave whatever data in a consistent state,
release any resources, and have all threads actually end.

A thread could not end from being blocked. In this case, I want to
distinguish between two different kinds of blocking - "temporarily"
blocking on an external thing like a disk read, and blocking on
internal logic like on a condition variable. At this point in time, I
don't think any of us really care to solve the first problem of a
short, but blocking, system call like a file read. It'll finish up
relatively quickly no matter what we do, and trying to end it early is
way too much work for us at the moment. The blocking we care about is
inside userland where we might block indefinitely, waiting for some
other userland action to occur which might never occur because that
thread noticed the stop flag and died before completing that action.

So, I would guess we have two basic options to handle this. One, we
can make all userland logic blocking use timeouts. Every single
relevant condition variable wait, etc., would be on a timeout, and
after each timeout the stop flag is checked. This is quite tedious and
error prone, and more importantly it has bad performance
characteristics - the bigger the number the slower the response time,
but a small number means more busy waiting. Ideally, we would like to
avoid the rule of every blocking wait must use a timeout.

So, that leaves plan two. When a thread blocks on something in a sane
program, it expects to be woken up again. The problem comes when the
guy who was supposed to wake it up noticed the stop flag and died
early without doing whatever is required to wake up the blocked
thread. This is about as far as I got, but I was thinking about basic
interfaces, like queues, and I was wondering to myself if there was
some abstraction which could use RAII to ensure that we cannot die
without waking up the other side of the queue. This is where I'm
currently stuck. There's too many idioms and patterns for inter-thread
communication for me to be able to armchair reason about. The goal I
had was that correct usage in the non-stop case implies correct usage
for the stop case, but I don't really see how this could be
accomplished offhand.
 
J

Joshua Maurice

Ok. Where to start. Let me just type out what's going through my head
here, and maybe people can add.

The basic idea is that we have some threads doing work as part of a
"job". An external thread has access to the job object, and it can
issue a stop request on the job. The threads doing the job need to
periodically check some global-like stop flag, and if it's set, then
they need to do the stop - leave whatever data in a consistent state,
release any resources, and have all threads actually end.

A thread could not end from being blocked. In this case, I want to
distinguish between two different kinds of blocking - "temporarily"
blocking on an external thing like a disk read, and blocking on
internal logic like on a condition variable. At this point in time, I
don't think any of us really care to solve the first problem of a
short, but blocking, system call like a file read. It'll finish up
relatively quickly no matter what we do, and trying to end it early is
way too much work for us at the moment. The blocking we care about is
inside userland where we might block indefinitely, waiting for some
other userland action to occur which might never occur because that
thread noticed the stop flag and died before completing that action.

So, I would guess we have two basic options to handle this. One, we
can make all userland logic blocking use timeouts. Every single
relevant condition variable wait, etc., would be on a timeout, and
after each timeout the stop flag is checked. This is quite tedious and
error prone, and more importantly it has bad performance
characteristics - the bigger the number the slower the response time,
but a small number means more busy waiting. Ideally, we would like to
avoid the rule of every blocking wait must use a timeout.

So, that leaves plan two. When a thread blocks on something in a sane
program, it expects to be woken up again. The problem comes when the
guy who was supposed to wake it up noticed the stop flag and died
early without doing whatever is required to wake up the blocked
thread. This is about as far as I got, but I was thinking about basic
interfaces, like queues, and I was wondering to myself if there was
some abstraction which could use RAII to ensure that we cannot die
without waking up the other side of the queue. This is where I'm
currently stuck. There's too many idioms and patterns for inter-thread
communication for me to be able to armchair reason about. The goal I
had was that correct usage in the non-stop case implies correct usage
for the stop case, but I don't really see how this could be
accomplished offhand.

Ok. To continue. We're concerned about stuff like the following case:

Lock lock(mutex);
while ( ! condition())
{ //no timeout
wait(condition_variable, mutex);
}

In this case, on the normal execution path for a correct program,
there is another thread which will acquire mutex, change condition to
be true, and signal condition_variable. The problem is that on the
stop request execution path, that other thread may not change the
condition to true and signal that condition_variable. Hell, during a
stop request, the producer thread may not even make it to the queue
loop - the creating thread may have just noticed the stop request and
never made the producer thread, or the producer thread noticed the
stop flag before it made it to the queue loop, etc.

I think we can all agree that, give no timeout waits, the consumer
needs to be changed to look like:

Lock lock(mutex);
for (;;)
{ if (job_is_stopped())
throw stop_exception();
if (condition())
break;
wait(condition_variable, mutex);
}

For a concurrent_queue, you would need to change the consumer to check
the stop flag before processing what might be a sentry value sent by
another thread to get it to wake up to notice the stop flag.

for (;;)
{ Work work = queue.atomicBlockingTopAndPop();
if (job_is_stopped())
throw stop_exception();
if (is_no_more_work_sentry(work))
break;
do_work(work);
}

For a semaphore, the code "if (job_is_stopped()) throw
stop_exception();" should be sprinkled about liberally.

Presumably something similar must be done for the blocking side of any
other blocking inter-thread communication abstraction.

What is important to me is where is the code which wakes up these
blocking consumers so the blocking consumer can check the stop flag?
We can discuss whether we can sentry values for the queue, or for it
to return a pair so pair.second can signal a sentry value, or add
functions "stop", or whatever variation. The hard part is ensuring
that whatever scheme you used it called on every possible stop path.
For this, I think we need to think about this in a different way. We
need to define a new responsibility like object ownership.

For the queue example, at some point the queue is made, and then it is
published in a safe way to the producers and blocking consumers. After
this handoff process, consumer threads start blocking on the queue, so
during this handoff process, someone must gain the additional
responsibility to wake up all consumer threads during a stop. I see
this very much akin to object ownership, and I do want to use RAII to
solve but. However, I don't know who should gain this responsibility,
or where it should be. Those answers depend greatly on how the
producers and consumers are started, how the queue is safely published
to them, etc. The question then becomes "What is good practice
concerning this?", "Any good idioms or patterns?", and "Is this
feasible and maintainable, or are we going to get some indefinitely
blocked threads sometimes when we stop a job?".

PS: I'm still thinking about how non-sequentially-consistent atomics
play into this. At the very least, let's consider the
condition_variable case:

Lock lock(mutex);
for (;;)
{ if (job_is_stopped())
throw stop_exception();
if (condition())
break;
wait(condition_variable, mutex);
}

If that stop flag does not have all writes and reads sequentially
consistent, then it's possible for my above idea to fail.
Specifically, there is a thread X which has responsibility to wake up
the blocked consumer on a stop. Suppose that thread notices a stop,
then signals that condition variable. Could that consumer thread wake
up and not notice the stop flag? From my (somewhat poor)
understanding, yes. It could go right back to sleep. In fact, it could
require an arbitrary number of broadcasts when the stop flag does not
have sequential consistency. Non-sequential-consistency makes this
even harder to reason about, and I'm not sure I know anything useful
at this point. I just know enough to know I'm screwed.
 
I

Ian Collins

I'm grappling with the same problem now. In short, all thread
cancellation in C++ is cooperative, and cooperative thread
cancellation is a bitch. Even if you had access to all of the source
code of your project, correctly identifying all spots which block to
implement cooperative thread cancellation seems daunting.

Which is why the standard advice is don't use thread cancellation!

It is seldom the best solution in an MT design.
 
J

Joshua Maurice

Which is why the standard advice is don't use thread cancellation!

It is seldom the best solution in an MT design.

Let's suppose my company has an engine which executes SQL from backing
federated data stores, including relational databases of many flavors,
flat files, etc. Let's further suppose that the user wants a "cancel"
button. This should stop the engine processing so that the CPU,
memory, disk, network, and other hardware resources are available to
run another SQL which the user may type in immediately following a
cancel. How would you do this? Or is this one of those rare times
cancel is a good idea?

I was thinking about it more, and thought of another couple of angles.
You could wrap the standard library to provide cancellation points
(eww).

Alternatively, thread cancellation without timed waits or wrapping the
std might be more maintainable if you keep the number of effective
cancellation points to a bare minimum. Specifically, you don't
sprinkle "if (stopped()) throw stop_exception();" everywhere, and
instead keep the number of effective cancellation points to a very
small number. However, it just seems to be another way to sacrifice
responsiveness (larger blocks of uninterruptable work, like timed
waits) for better maintainability (like always-timed waits).
 
S

Scott Meyers

flat files, etc. Let's further suppose that the user wants a "cancel"
button.

Or you're concurrently running multiple algorithms to solve the same problem,
and once you have a solution, you want to stop the execution of the threads
still working on the now-solved problem.

Scott
 
S

Scott Meyers

For this, I think we need to think about this in a different way. We
need to define a new responsibility like object ownership.

For the queue example, at some point the queue is made, and then it is
published in a safe way to the producers and blocking consumers. After
this handoff process, consumer threads start blocking on the queue, so
during this handoff process, someone must gain the additional
responsibility to wake up all consumer threads during a stop. I see
this very much akin to object ownership, and I do want to use RAII to
solve but. However, I don't know who should gain this responsibility,
or where it should be.

I see the ownership analogy, but I think that scope-based ownership like RAII
may be barking up the wrong tree. The problem smells much more like shared
ownership to me. With shared_ptr, shared_future+promise, etc., the entity
responsible for resource release is the one that happens to be there when the
reference count goes to zero. In your case, the entity responsible for waking
all blocked interruptible threads is the thread that happens to detect that the
stop flag has become true. It's more complicated than that, of course, because
more than one thread may detect that condition, and threads that are thus
awakened may detect it, too, and we probably don't want them also trying to wake
everybody, but my fundamental point is that while RAII may play a role (as it
does in reference counting), I think that what you mean by ownership is more
likely to be dynamically determined.

Scott
 
J

Joshua Maurice

I see the ownership analogy, but I think that scope-based ownership like RAII
may be barking up the wrong tree.  The problem smells much more like shared
ownership to me.  With shared_ptr, shared_future+promise, etc., the entity
responsible for resource release is the one that happens to be there when the
reference count goes to zero.  In your case, the entity responsible for waking
all blocked interruptible threads is the thread that happens to detect that the
stop flag has become true.  It's more complicated than that, of course, because
more than one thread may detect that condition, and threads that are thus
awakened may detect it, too, and we probably don't want them also trying to wake
everybody, but my fundamental point is that while RAII may play a role (as it
does in reference counting), I think that what you mean by ownership is more
likely to be dynamically determined.

Of course. Not single ownership, but preferably something natural and
exception safe.
 
K

Keith H Duggar

Let's not go to extreme relativization (or what is it called ;-).  Writing a
container (or most library stuff) involves many lines of code.

Inserting critical sections where they are needed is just a few lines.
Working with traditional objects in the MT environment is not a big deal, if
your overall design is sound, and you did identify the points of sharing.

Using a magic class that just does to good thing is cool. But replacing the
traditional way with some half-halping thing will hurt more than help,
believe me.

And if they don't believe you they need only hop else-thread and
read the twisted Gordian Knot Joshua entangled around himself by
focussing on preemptive thread/lock based solutions rather than
cooperative process/message based paradigms.

Happily the ever increasing focus of the computational community
on general /distributed/ computing is forcing efforts more toward
asynchronous message based computation and away from synchronous
shared data gymnastics.

To paraphrase Ward Cunningham "the Actor Model is a computational
model, multi-threading is a career path". [1]

http://en.wikipedia.org/wiki/Actor_model

KHD

[1] The original quote being "S-expressions are a representation,
XML is a career path." -- Ward Cunningham, 2003
 
J

Joshua Maurice

And if they don't believe you they need only hop else-thread and
read the twisted Gordian Knot Joshua entangled around himself by
focussing on preemptive thread/lock based solutions rather than
cooperative process/message based paradigms.

Happily the ever increasing focus of the computational community
on general /distributed/ computing is forcing efforts more toward
asynchronous message based computation and away from synchronous
shared data gymnastics.

Oh, I think I'm starting to agree. Reducing it all down to concurrent
queues seems to make life a whole lot easier.
 
T

tni

A thread could not end from being blocked. In this case, I want to
distinguish between two different kinds of blocking - "temporarily"
blocking on an external thing like a disk read, and blocking on
internal logic like on a condition variable. At this point in time, I
don't think any of us really care to solve the first problem of a
short, but blocking, system call like a file read. It'll finish up
relatively quickly no matter what we do, and trying to end it early is
way too much work for us at the moment. The blocking we care about is
inside userland where we might block indefinitely, waiting for some
other userland action to occur which might never occur because that
thread noticed the stop flag and died before completing that action.

That's a bad assumption, system calls can block for an arbitrary amount
of time. Just try accessing a file on an NFS mount and disconnect the
network cable...
 
A

Alexander Terekhov

Joshua Maurice wrote:

[... cancellation ...]

You should google pthread_cancel_e and/or pthread_exit_e.

Hth.

regards,
alexander.
 
J

Joshua Maurice

Joshua Maurice wrote:

[... cancellation ...]

You should google pthread_cancel_e and/or pthread_exit_e.

Hth.

From what I understand, pthread cancellation is a mess. Besides, I
thought we were talking about cancellation in C++0x, not POSIX.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,774
Messages
2,569,596
Members
45,143
Latest member
SterlingLa
Top