Re: std::string bad design????
Lourens Veen wrote:
Le Chaud Lapin wrote:
If you write map<> so that it uses global variables, and you
try to use map<>'s in a multi-threaded application, you
already know that there will be problems.
However, if the C++ standard says nothing about whether std::map<>
uses global variables, then you _can't_ know whether there will be
problems.
Actually, its pretty certain that any implementation which meets
the intent of the standard (which is that freed memory actually
does get reused in certain cases) almost must use static
variables. I don't think you can manage a free space arena
without them. (And map<> is required to use dynamically
allocated memory.)
James Kanze wrote:
You might want to check
http://www.sgi.com/tech/stl/thread_safety.html; I'm pretty sure
that this corresponds to the thinking of the committee with
regards to how thread safety will be defined in the library.
(Except some special cases, like maybe std::cerr, and almost
certainly std::exit().)
I will take a look, but there is no need. I can write a class Foo,
right now, make it so that it uses a global variable, run multiple
threads against it, and watch my program crash. I will not be
surprised in the least. I can also write a function Bar, make it so
that it uses a static local (global) variable, run multiple-threads
against it, and watch my program crash. I will not be surprised in
the least.
Duh. Neither will we. The point is that you could write a function
Bar, make it so that it uses a static local (global) variable,
_protect_it_using_a_mutex_, run multiple threads against it, and
watch the programme crash. I bet that that _would_ surprise you.
However, there is nothing in the C++ standard that prevents this from
happening.
The point is that you can write a function with NO static
variables, and still get a program crash, because the compiler
generated code and the library may (and in fact almost certainly
will) use static data behind your back.
Microsoft's approach to this was to provide two libraries:
one for single-threaded applications. one for
multi-threaded applications. It is the same approach I
take. Fortunately, 98%+ of my classes are already
"thread-safe", meaning, they do not use global variables as
part of their implementation unless they have to. I have
found that most classes can fit this model, except for
things like random number generators, or classes that
require massive global state to help it, like
Integer::is_prime() which works fastest if it is allowed to
maintain a global static array of small primes, say those
less than 60,000. But that array is declared const and
never changes, so it is immune to requiring a critical
section (spin-lock with failover to mutex).
Really? Who says that accessing a const array from various threads at
the same time won't cause a crash?
Posix.
Or rather, Posix says that a conforming implementation must
support concurrent access to an object if no access attempts to
modify the object. (Note that in this context, something like
"i = 1" is a modification even if i's previous value was already
1. I think that this is obvious to both you and Chaud Lapin,
but it's probably worth mentionning just in case.)
Of course, if your running under Windows, or VMS, or some other
OS, the Posix guarantee doesn't hold. I think that Windows, at
least on a PC, does make this guarantee, although I don't think
I've ever seen it in writing. (But then, I've not looked at the
Windows guarantees too much in detail, since it's pretty much an
irrelevant platform in my field of work.)
Note that this guarantee actually imposes certain requirements
on the hardware, even though it is part of the OS specification.
What if this system has two processors, and your const array
is stored in a ROM connected to a bus shared between the
processors? Two simultaneous reads could very well mess up the
bus's addressing logic and crash the system.
If that's how the hardware works, you can't implement Posix on
it. (In practice, such hardware exists, but it is usually
accessed logically as a file, rather than as memory.)
But for your std::basic_string example, note that, if the
implementation of std::basic_string used a global variable,
I would never place the burden of supplying mutexes on the
user of that component. Again, I would follow Microsoft's
approach, and provide a library for single-threaded
applications, and one for multi-threaded applications. The
single-threaded application library would not have
protection. The multi-threaded one would. This works very
well today.
Well, in practice it seems to mostly work. But it is undefined
behaviour, and there is no guarantee that a conforming
compiler won't mess it up.
I think that Microsoft makes certain guarantees (although I've
never actually seen the specification of what). Posix definitly
does:
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. The following functions
synchronize memory with respect to other threads:
[...]
The pthread_once() function shall synchronize memory for the
first call in each thread for a given pthread_once_t object.
Unless explicitly stated otherwise, if one of the above
functions returns an error, it is unspecified whether the
invocation causes memory to be synchronized.
Applications may allow more than one thread of control to
read a memory location simultaneously.
It's limited, and it only applies to C (not to C++), but it's a
start.
I think that there is pretty much a consensus in the C++
committee that the language guarantees should more or less be
based on these Posix guarantees (although there may be some
issues as to what they mean when applied to certain C++
constructs).
[...]
If F calls G, G calls H, and H calls F, then spilling to
static memory in F would result in a dead end. The stack
has to be used.
So build a second stack in static memory. That would support recursion
just fine, and still break threading.
Easier than that: just make sure that you haven't spilled when
you call another function.
Note that most modern machines have very good hardware support
for based addressing; use the local stack frame pointer as a
base, and the resulting code will often be smaller and faster
using stack based variables than for static data. This wasn't
true in the past, and on some older architectures, accessing
stack based data could be very expensive. (On an 8080,
accessing a stack based 16 bit int probably cost about 10 times
the time of accessing a static one.) A compiler writer will
have to make some choices here, and spilling to static memory is
definitly an option to be considered.
--
James Kanze (GABI Software) email:james.kanze@gmail.com
Conseils en informatique orient?e objet/
Beratung in objektorientierter Datenverarbeitung
9 place S?mard, 78210 St.-Cyr-l'?cole, France, +33 (0)1 30 23 00 34
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]