From: Christopher Pisz on
If I am OT here, please refer me. This NG was my best guess. I didn't see
any "Multithreading" groups and my questions do after all pertain to OO
design.

I discovered a few catch phrases that may point to the information I need.
Those phrases are "Object Level Locking", "Class Level Locking", and "RCU",
all of which I don't really understand and would love to see some code
examples and better references for. In the book "C++ Modern Design", the
author talks a little about the subject, but an implementation outside of
his own "Loki Library" is not shown and leaves my mind full of question
marks.

I am trying to solve a real world problem. This problem has come up in more
than one more domain. The problem is that there is a need for a C++:

1) Dynamic sized container, for example a class that has a std::map member.
A) multiple threads can make method calls that find, insert, and erase
elements from the map.
B) multiple threads can grab references and mutate elements from the
container.

I understand that 1A can be achieved using a static mutex in the class that
has the map member. I can use that mutex in every public member that
accesses the map. However, I have no clue how to achieve 1B, even in
psuedocode. 1B has come up at my workplace and in my hobby projects and I
want to be the hero who tackles it!

Please help with any info, references, or psuedocode you can. Thanks!





From: Daniel T. on
"Christopher Pisz" <someone(a)somewhere.net> wrote:

> If I am OT here, please refer me. This NG was my best guess. I didn't see
> any "Multithreading" groups and my questions do after all pertain to OO
> design.
>
> I discovered a few catch phrases that may point to the information I need.
> Those phrases are "Object Level Locking", "Class Level Locking", and "RCU",
> all of which I don't really understand and would love to see some code
> examples and better references for. In the book "C++ Modern Design", the
> author talks a little about the subject, but an implementation outside of
> his own "Loki Library" is not shown and leaves my mind full of question
> marks.
>
> I am trying to solve a real world problem. This problem has come up in more
> than one more domain. The problem is that there is a need for a C++:
>
> 1) Dynamic sized container, for example a class that has a std::map member.
> A) multiple threads can make method calls that find, insert, and erase
> elements from the map.
> B) multiple threads can grab references and mutate elements from the
> container.
>
> I understand that 1A can be achieved using a static mutex in the class that
> has the map member. I can use that mutex in every public member that
> accesses the map. However, I have no clue how to achieve 1B, even in
> psuedocode. 1B has come up at my workplace and in my hobby projects and I
> want to be the hero who tackles it!
>
> Please help with any info, references, or psuedocode you can. Thanks!

You do it the same way. Any thread that is using an element from the
container must lock the container, so that the element can't be deleted
by another thread.

Of course you want to minimize such locking as much as possible. You say
that the map is contained in a class and presumably there is a mutex
associated with each object of the class which is locked whenever the
class is in one of its functions. The first thing you need to do is
remove any possibility of accessing the map except by objects of that
class. Make the map private, and don't provide any getters that provide
a reference or pointer to the map *or any of its contents*. Also make
sure that no iterators into the map are accessible to objects that are
not of this class.

Just a quick note, the above advice is standard OO practice (i.e., data
hiding/LoD.)
From: Dmitry A. Kazakov on
On Sat, 12 Jan 2008 11:28:26 -0600, Christopher Pisz wrote:

> I am trying to solve a real world problem. This problem has come up in more
> than one more domain. The problem is that there is a need for a C++:
>
> 1) Dynamic sized container, for example a class that has a std::map member.
> A) multiple threads can make method calls that find, insert, and erase
> elements from the map.
> B) multiple threads can grab references and mutate elements from the
> container.
>
> I understand that 1A can be achieved using a static mutex in the class that
> has the map member. I can use that mutex in every public member that
> accesses the map. However, I have no clue how to achieve 1B, even in
> psuedocode. 1B has come up at my workplace and in my hobby projects and I
> want to be the hero who tackles it!

The references have types and if they are concurrently accessed then you
need locking at the level of these types and objects of. In general you
have (all mutable):

1. Container type

2. Element type

3. Reference type (a proxy to an element in the container)

4. Pointer type (same as 3, but lower-level)

Each one object of the types 1..4 must be locked upon mutation. The choices
are between:

A. Per object (instance) locks

B. Per type locks

C. Global lock

The problem is that pointers and built-in references aren't proper abstract
types in C++, so you have to avoid them altogether when concurrency is in
question. I don't even consider 4. Under 3 a smart reference is meant.

The choice between A, B, C is determined by each concrete case. A is
preferable when you have to lock things for a long time. C is on the other
end of the scale. B is in between. The problem with A is that you have to
handle cross operations (like swapping elements of different containers)
and you can easily run into a deadlock if you do not apply some policy of
locking.

Alternatives:

A viable alternative is to avoid mutability. Scrap 3 and 4. Use by value
semantics for 2, and you will go with A when only 1 is left (and no
operations between containers allowed).

On this path you can switch to immutable containers. That is a sort of
transaction model when a thread dealing with the container becomes a copy
of it (opens transaction). Then it does what needed and commits changes
(closes transaction). This requires a higher level abstraction than just a
container.

You can switch from protected objects to monitors. A monitor is a thread
exclusively responsible for dealing with the container (resource in
general). Anybody who wants to do anything with the container and its
elements requests the monitor via rendezvous. This also requires a
higher-level model, because monitors are quite inefficient for fine
granularity concurrency.

In general case, there is no optimal design for concurrently accessed
containers. Typically, first when you constrain the container's
functionality to only things you need and glue individual operations into
larger ones, then you will be able to make some choices.

--
Regards,
Dmitry A. Kazakov
http://www.dmitry-kazakov.de
From: Martin Vuille on
"Christopher Pisz" <someone(a)somewhere.net> wrote in
news:4788f8ba$0$22634$4c368faf(a)roadrunner.com:

> I didn't see any "Multithreading" groups and my questions do

comp.programming.threads

MV

--
I do not want replies; please follow-up to the group.
From: Patrick May on
"Dmitry A. Kazakov" <mailbox(a)dmitry-kazakov.de> writes:
> On Sat, 12 Jan 2008 11:28:26 -0600, Christopher Pisz wrote:
>> I am trying to solve a real world problem. This problem has come up
>> in more than one more domain. The problem is that there is a need
>> for a C++:
>>
>> 1) Dynamic sized container, for example a class that has a std::map
>> member.
>> A) multiple threads can make method calls that find, insert, and
>> erase elements from the map.
>> B) multiple threads can grab references and mutate elements from
>> the container.
[ . . . ]
> The references have types and if they are concurrently accessed then
> you need locking at the level of these types and objects of. In
> general you have (all mutable):
>
> 1. Container type
>
> 2. Element type
>
> 3. Reference type (a proxy to an element in the container)
>
> 4. Pointer type (same as 3, but lower-level)
>
> Each one object of the types 1..4 must be locked upon mutation. The
> choices are between:
>
> A. Per object (instance) locks
>
> B. Per type locks
>
> C. Global lock

Further, you may find that you need read-only and read-write
locks to reduce contention and improve concurrency. Then you'll need
transactional attributes to allow dirty reads. And so on and so on.
Here there be dragons.

> The problem is that pointers and built-in references aren't proper
> abstract types in C++, so you have to avoid them altogether when
> concurrency is in question. I don't even consider 4. Under 3 a smart
> reference is meant.
>
> The choice between A, B, C is determined by each concrete case. A is
> preferable when you have to lock things for a long time. C is on the
> other end of the scale. B is in between. The problem with A is that
> you have to handle cross operations (like swapping elements of
> different containers) and you can easily run into a deadlock if you
> do not apply some policy of locking.

Mr. Kazakov's advice is excellent. Here he hints at an important
point: Why do you need this functionality? If you're building tools
for other developers, you'll need this level of generality and can
justify the time and effort to address all these issues. If, on the
other hand, you're trying to solve a particular problem, this might be
a good time to take a step back and look at your real requirements.
Do you really need transactional collections or is that simply an
artifact of your proposed solution?

> A viable alternative is to avoid mutability. Scrap 3 and 4. Use by
> value semantics for 2, and you will go with A when only 1 is left
> (and no operations between containers allowed).
>
> On this path you can switch to immutable containers. That is a sort
> of transaction model when a thread dealing with the container
> becomes a copy of it (opens transaction). Then it does what needed
> and commits changes (closes transaction).

This obviously only works for relatively small collections of
objects, even if only references or smart pointers are copied. You
may also want to consider making the nodes in the collection
immutable, avoiding the complexity of allowing updates. Again, it
depends on your underlying requirements.

> In general case, there is no optimal design for concurrently
> accessed containers. Typically, first when you constrain the
> container's functionality to only things you need and glue
> individual operations into larger ones, then you will be able to
> make some choices.

More excellent commentary. If you're interested in a completely
different approach, you might want to look at JavaSpaces. The
reference implementation is in Java, but it's relatively
straightforward to apply the concepts to any language. A space can be
used as a shared collection and provides the transactional isolation
and concurrent access you require.

Regards,

Patrick

------------------------------------------------------------------------
S P Engineering, Inc. | Large scale, mission-critical, distributed OO
| systems design and implementation.
pjm(a)spe.com | (C++, Java, Common Lisp, Jini, middleware, SOA)