Re: Multitasking in C++...
werasm wrote:
Maxim Yegorushkin wrote:
[]
I wonder why you need a counting
semaphore, rather than a mutex + condition.
I suppose a counting sem was an overkill - you are right.
Maybe/probably even slows things down.
I would not expect any noticeable performance hit in either case.
The queue does have that
knowledge (to know that it is empty).
AFAIK, a queue can be implemented using any of the two fundamental data
structures: array or list. You can tell whether it is empty easily. Is
it not the case for you?
Allocating memory may block a thread. Does it mean that you are
allocating from a preallocated pool?
Yes, it does :-). In this environment, there are unfortunately 2 more
decisions that you have to make:
1. How much are you willing to queue. I only queue cloned pointers,
btw, which makes things quite efficient.
2. How much you are willing to queue, and the sizes of your commands
and their arguments are an indication of how much memory you need to
pre-allocate, sadly.
I must mention that I have started looking at using boost pools over
the range of sizes expected. Example:
[]
If a pool was not provided for a size required, we loan from the heap
and it is logged - very rare. The pools have an initial size estimate,
and grow as required. We don't have this scheme currently though, we
have a scheme of queues (thread safe, of course) that are pre-allocated
(by guessing). We have a little bit of pointer hiding in the memory to
the stack to which the memory must return, and when looking for a
specific size, we use a binary seach. If a specific size does not
exist, we lend from the next up until we find a pool that has
something, else we lend from the heap - which is very rare. I have
toyed with this in comparison too the boost::pool. I think it was very
similar wrt efficiency. boost::pool is more scalable if one gets the
sizes wrong, though (in that the heap is less likely to be used).
Be aware, that by sharing blocks allocated by boost::pool you can
inflict false sharing, that is, if you modify an object which happens
to reside in the the same cache line with the object used by another
thread, you are effectively playing ping-pong between processors on
smp. There are allocators designed for multithreaded applications which
try to mitigate the problem. http://www.hoard.org/ provides docs which
discuss the issue in detail.
That is very interesting. How do you know if a pointer or a reference
argument refers to a heap or a stack allocated object? The size of the
object being pointed to can not tell you that, can it?
Yes, good question. The only reason we consider the size, is that for
large objects, we want to prevent additional copying. For small
objects, we send a copy of the argument over. For large objects we send
a pointer over (and only perform one copy) - the joy of templates.
sizeof(std::list<int>) == 8 for a 32-bit system using GNU C++ standard
library. Yet, passing the list by value can be costly if it contains a
large number of elements. In other words, the size of the objects tells
you nothing about how expensive copy operation may be. Am I missing
something?
For
references (if the callback receives are reference), we store a copy or
a pointer to a copy. We never store the reference, as the mistake of
sending a reference that exist on a calling stack can easily be made.
Simple, if <T&> typedef T ArgT; Then, after that, the size of ArgT
determines the storage type, so to speak. For pointer storage types,
ownership is released after execution, and the cloned command receives
ownership, until it is destroyed after execution. All arguments are
also pre-allocated, if they happen to be pointers due to large size (a
thing to determine is at what size it become worth not storing the
actual type - this is currently a thumbsuck).
I don't understand why you need to do that. Why not just take
everything by value, just like boost::bind does? In this case the user
has total control and transparency: if he wants to pass an argument by
reference he just pass a (smart) pointer.
[]
Many has in-house implementations of something like that.
I was not necessarily aware, but was wondering along those lines. Hence
my idea of standardising. I'm sure they could provide one with some
good options if they try. They being the ones that realize the standard
- us? I think the idea is good, but I don't think I'm the best
implementor - not to bad, I suppose, but there are better people suited
for the job :-). I thinking people of the likes of erdani etc. I did do
our inhouse implementation though, and it promises more than what is
currently available (IMhO).
There are quite a big number of choices and implementation strategies,
I don't think there would be one thing that provides the level of
abstraction and overhead good enough for everyone.
Hmmm, Java was brave enough to try something. I have not yet looked
what C++/CLI has come up with. I venture something like GC would be
beneficial. I especially like the notion of two phased destruction,
where true ownership exists (and hence deletion), but all references to
the original become aware of its non-existance, which they can test
before calling. This is more-or-less what weak pointer gives you (wish
it was standard). It would be ideal for this use, typically.
For example, the fact, that the queue holds a weak, not string
reference to the command, is more of a policy rather than of a
mechanism. Queues can be implemented using containers + synchronization
primitives or using platform mechanism like local datagram sockets.
Yes, true - but providing a consistent interface (dictating an
interface), will allow various implementations to do the same thing.
Typically, one only need a container of pointers for this exercise,
ever (behind a consistent notion of something like a CmdSvr).
Three different implementation of queues / worker threads have been
used in my projects. They serve different purposes and their interfaces
reflect the fact. I could not provide the same interface for all. This
may be just my fault :)
I believe we have everything in c++ now required to build a custom
infrastructure of this kind quite easily. For example, the one you
described, could be implemented using boost::thread for threading, std
containers, boost::function + boost::bind for commands, boost smart
pointers to implement a specific command lifetime policy.
Yes, as we have. But standard would be so-o-o-o much nicer. How about
it. Incorporating a nice simple mechanism that abstracts OOD from
Concurrent D. Our inhouse scheme was easily portable between 3 very
different platforms. Java (what a bore of a language:-) - maybe just
kidding) provides more wrt. architecture. The unwillingness to get
things wrong the first time prevents C++ from providing a solution at
all.
I agree that it would be beneficial for new starters. Java requites a
different mind set, their decisions may not be in line with c++
philosophy.
boosts constructs are at too a low level, but I agree, could be
used. My tasks look very different to theirs though, but probably
because I did not learn from POSIX.
The beauty of boost components, IMO, that they are simple fundamental
mechanisms from which you can compose any application level policy you
like.
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]