Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Will Modern C++ Design ever get fixed?

67 views
Skip to first unread message

Witold Kuzminski

unread,
Oct 2, 2010, 10:03:08 AM10/2/10
to
I have recently browsed thru the Modern C++ Design in a bookstore, and
noticed it was the 17-th printing, from February 2009. I have checked
the somewhat less stellar part of the book, where the author writes:
"Very experienced multithreaded programmers know that even the Double-
Checked Locking pattern, although correct on paper, is not always
correct in practice. [more along those lines]"
To my astonishment, the text in the book has been changed as compared
with what I bought in 2001, and it no longer talks about "RISC code
arrangers", but about "certain symmetric multiprocessor environments"
instead.

Typing 'volatile' in google's 'Search this group' brings this
discussion: "Am I or Alexandrescu wrong about singletons?", which is
from March of this year. Browsing thru that thread is not really
encouraging.

I do know very well, that the author has corrected himself on those
issues.
Even in that thread, he points to his paper (with Scott Meyers) from
2004:
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf

The problem seems to be, that the book still contains all those
misconceptions.
I mean the 17-th printing of the book, from February 2009, with the
text in those very paragraphs changed(!!!), except where it would
matter.

The reason I bring it up, is because my own experience shows, that
there is great number of C++ programmers, who believe in DCLP and
volatile when programming with threads. Very many of them, if not
most, do not point to Schmidt nor MSDN as the source of their beliefs.
They point at Modern C++ Design.

I suspect, that by allowing for his book to spread all those
misconceptions (in 2010!!!), the author could be doing more harm, then
he is doing good, with the otherwise great content, when he stays away
from threads.

--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]

Mathias Gaunard

unread,
Oct 3, 2010, 3:11:54 AM10/3/10
to
On Oct 2, 3:03 pm, Witold Kuzminski <witold.kuzmin...@gmail.com>
wrote:

> The reason I bring it up, is because my own experience shows, that
> there is great number of C++ programmers, who believe in DCLP and
> volatile when programming with threads. Very many of them, if not
> most, do not point to Schmidt nor MSDN as the source of their beliefs.
> They point at Modern C++ Design.

volatile is utterly useless for multithreaded programming, since it
doesn't perform a memory barrier which is necessary in order to have
cache coherency on SMP systems.

Note that with MSVC, however, volatile does do memory barriers; but
other compilers don't do it, and the C++0x standard, which provides
full support for multithreading, doesn't either.

nm...@cam.ac.uk

unread,
Oct 3, 2010, 11:03:32 AM10/3/10
to
In article <a9623667-5870-4e62...@d17g2000yqm.googlegroups.com>,

Mathias Gaunard <louf...@gmail.com> wrote:
>On Oct 2, 3:03 pm, Witold Kuzminski <witold.kuzmin...@gmail.com>
>wrote:
>
>> The reason I bring it up, is because my own experience shows, that
>> there is great number of C++ programmers, who believe in DCLP and
>> volatile when programming with threads. Very many of them, if not
>> most, do not point to Schmidt nor MSDN as the source of their beliefs.
>> They point at Modern C++ Design.
>
>volatile is utterly useless for multithreaded programming, since it
>doesn't perform a memory barrier which is necessary in order to have
>cache coherency on SMP systems.

More precisely, it is utterly useless in portable code or where the
implementation does not define suitable semantics. To a first
approximation, its semantics are entirely implementation dependent
(regrettably, not even implementation-defined). The wording in
C++0X 1.9 Program execution paragraph 12 makes it clear that volatile
is not defined as a parallelism primitive.

>Note that with MSVC, however, volatile does do memory barriers; but
>other compilers don't do it, and the C++0x standard, which provides
>full support for multithreading, doesn't either.

Does it specify that it does so?


Regards,
Nick Maclaren.

Bo Persson

unread,
Oct 4, 2010, 1:25:06 AM10/4/10
to
nm...@cam.ac.uk wrote:
> In article
> <a9623667-5870-4e62...@d17g2000yqm.googlegroups.com>,
> Mathias Gaunard <louf...@gmail.com> wrote:
>
>> Note that with MSVC, however, volatile does do memory barriers; but
>> other compilers don't do it, and the C++0x standard, which provides
>> full support for multithreading, doesn't either.
>
> Does it specify that it does so?

It does specify this, as a compiler specific extension.

http://msdn.microsoft.com/en-us/library/12a04hfd%28v=vs.80%29.aspx


Bo Persson

Witold Kuzminski

unread,
Oct 4, 2010, 1:24:40 AM10/4/10
to
On Oct 3, 11:03 am, n...@cam.ac.uk wrote:
> In article <a9623667-5870-4e62-b108-b6fbc1a68...@d17g2000yqm.googlegroups.com>,
> Mathias Gaunard <loufo...@gmail.com> wrote:

> >Note that with MSVC, however, volatile does do memory barriers; but
> >other compilers don't do it, and the C++0x standard, which provides
> >full support for multithreading, doesn't either.
>
> Does it specify that it does so?

Irrespective of the memory barriers, according to Herb Sutter,
Microsoft tried to 'strengthen' volatile, but it didn't quite work, so
he recommends 'to leave volatile alone'. I quote from that other
thread I mentioned in the OP.

Mathias Gaunard

unread,
Oct 4, 2010, 1:25:24 AM10/4/10
to
On Oct 3, 4:03 pm, n...@cam.ac.uk wrote:

> >Note that with MSVC, however, volatile does do memory barriers; but
> >other compilers don't do it, and the C++0x standard, which provides
> >full support for multithreading, doesn't either.
>
> Does it specify that it does so?

Microsoft does specify that it performs the matching acquire and
release barriers depending on whether you read the variable or write
to it.
See <http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx>

Andre Kaufmann

unread,
Oct 4, 2010, 1:25:58 AM10/4/10
to
On 03.10.2010 09:11, Mathias Gaunard wrote:
> On Oct 2, 3:03 pm, Witold Kuzminski<witold.kuzmin...@gmail.com>
> wrote:
>
> [...]

> Note that with MSVC, however, volatile does do memory barriers; but
> other compilers don't do it, and the C++0x standard, which provides
> full support for multithreading, doesn't either.

I don't think volatile does do memory barriers in MSVC.
It "simply" prevents the compiler from doing register optimizations,
which might be relevant for multithreading.

Andre

Andre Kaufmann

unread,
Oct 4, 2010, 4:29:06 PM10/4/10
to
On 04.10.2010 07:25, Mathias Gaunard wrote:
> On Oct 3, 4:03 pm, n...@cam.ac.uk wrote:
>
>>> Note that with MSVC, however, volatile does do memory barriers; but
>>> other compilers don't do it, and the C++0x standard, which provides
>>> full support for multithreading, doesn't either.
>>
>> Does it specify that it does so?
>
> Microsoft does specify that it performs the matching acquire and
> release barriers depending on whether you read the variable or write
> to it.
> See<http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx>
>

The official MSVC method to insert memory barriers in code are the
intrinsics:

_WriteBarrier
_ReadBarrier

http://msdn.microsoft.com/en-us/library/65tt87y8(VS.80).aspx


Volatile, as I assume (don't know for sure) in most C++ compilers,
prevents certain optimizations. I've checked the generated code for x86
processors and the MSVC compiler does not emit any memory barrier code
when volatile is used. However, under IA64 or ARM processors this might
be different, but regarding to the documentation of volatile, I assume
it's not.

I think it would be either too complex for the compiler to emit
optimized memory barrier code or simply inefficient to insert memory
barrier code on each variable access.
E.g. when a complex object is shared by multiple threads, shall the
compiler emit memory barrier code each time a member variable is
accessed and what should happen if the code removes volatile with a
const_cast ?

Nethertheless, certain compiler optimizations (register optimizations,
reordering) have an impact under multithreaded applications and
therefore I think volatile is relevant for such applications, if only to
prevent certain compiler optimizations which are hard to track down.

(IIRC GCC does prevent these optimizations too, when volatile is used,
but are not sure about it)

Andre

Vladimir Jovic

unread,
Oct 4, 2010, 4:30:56 PM10/4/10
to
Andre Kaufmann wrote:
> On 03.10.2010 09:11, Mathias Gaunard wrote:
>> On Oct 2, 3:03 pm, Witold Kuzminski<witold.kuzmin...@gmail.com>
>> wrote:
>>
>> [...]
>> Note that with MSVC, however, volatile does do memory barriers; but
>> other compilers don't do it, and the C++0x standard, which provides
>> full support for multithreading, doesn't either.
>
> I don't think volatile does do memory barriers in MSVC.
> It "simply" prevents the compiler from doing register optimizations,
> which might be relevant for multithreading.

How are volatile relevant for multithreading?

Mathias Gaunard

unread,
Oct 4, 2010, 5:16:37 PM10/4/10
to
On Oct 4, 6:25 am, Mathias Gaunard <loufo...@gmail.com> wrote:

> Microsoft does specify that it performs the matching acquire and
> release barriers depending on whether you read the variable or write
> to it.
> See <http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx>

Actually, this isn't talking about CPU and barriers, only reordering,
which might just be a compiler optimization thing.
But some other sources I can find say it ensures the CPU does not
reorder either starting with VS 2005 (MSVC8).

See the following for example
<http://msdn.microsoft.com/en-us/library/ms686355.aspx>

Witold Kuzminski

unread,
Oct 5, 2010, 4:57:04 AM10/5/10
to
On Oct 4, 4:30 pm, Vladimir Jovic <vladasp...@gmail.com> wrote:

> How are volatile relevant for multithreading?

According to the author of Modern C++ Design:
"C++ provides the volatile type modifier, with which you should
qualify each variable that you share with multiple threads."

My point was _not_ that you have to spend $60 to learn the above in
2010, from one of the highly regarded C++ experts; it was rather:
there were changes made to the original text from 2001. And I suspect,
that the author should have understood the relevance of volatile no
later than early 2001.
Do not know about DCLP, but, please, this thing is known not to be
correct on paper to begin with.

So the author had made changes to the text from 2001, yet chose to
make changes elsewhere.
And thus, the natural question is: is that multithreaded nonsense ever
going to be fixed?

I think it is important, because the volatile multithreaded
programmers I meet from time to time all claim, that since they read
it in Modern C++ Design, it must obviously be true.

Chris M. Thomasson

unread,
Oct 5, 2010, 5:06:48 AM10/5/10
to
"Andre Kaufmann" <akfm...@t-online.de> wrote in message news:i8bmrn$eu6$03$1...@news.t-online.com...

> On 04.10.2010 07:25, Mathias Gaunard wrote:
>> On Oct 3, 4:03 pm, n...@cam.ac.uk wrote:
>>
>>>> Note that with MSVC, however, volatile does do memory barriers; but
>>>> other compilers don't do it, and the C++0x standard, which provides
>>>> full support for multithreading, doesn't either.
>>>
>>> Does it specify that it does so?
>>
>> Microsoft does specify that it performs the matching acquire and
>> release barriers depending on whether you read the variable or write
>> to it.
>> See<http://msdn.microsoft.com/en-us/library/12a04hfd%28VS.80%29.aspx>
>>
>
> The official MSVC method to insert memory barriers in code are the
> intrinsics:
>
> _WriteBarrier
> _ReadBarrier
>
> http://msdn.microsoft.com/en-us/library/65tt87y8(VS.80).aspx

I was under the impression that those are compiler barriers. Does
`_ReadWriteBarrier' actually emit a `MFENCE' or a dummy `XCHG' on an x86? I
never use them because all of my "critical code" is in externally assembled
libraries.

Vladimir Jovic

unread,
Oct 6, 2010, 1:44:58 AM10/6/10
to

In other words, you are putting volatile to prevent possible buggy
compiler optimizations.

1) Who guaranties you that volatile is going to solve these issues?

2) Why don't you just turn off the optimizations, if you think it
produces buggy code?

3) What about the programs that are not multi-threaded? They do not need
to worry about such problems, and they certainly don't need memory
barriers and fences (this looks like M$ terminology. I thought these are
called mutexes and semaphores)

Christophe de Dinechin

unread,
Oct 6, 2010, 1:41:55 AM10/6/10
to
> However, under IA64 or ARM processors this might
> be different, but regarding to the documentation of volatile, I assume it's not.

The 'volatile' keyword was defined in a context where the only thing
you really needed to care about was whether a value was held in a
register or committed to memory.

Modern architectures have more advanced memory semantics, notably
ordering. If left to its own device, a CPU will not necessarily
perform memory transactions in program order, if at all. This is
somewhat architecture-dependent, so it's hard to put in the language
in a standardized way. I guess this is why the trend for tool vendors
is to provide access through machine-specific headers and extensions.

tni

unread,
Oct 6, 2010, 1:53:47 AM10/6/10
to
On 2010-10-04 22:29, Andre Kaufmann wrote:
> Volatile, as I assume (don't know for sure) in most C++ compilers,
> prevents certain optimizations. I've checked the generated code for x86
> processors and the MSVC compiler does not emit any memory barrier code
> when volatile is used.

It doesn't, because it's not necessary. x86/x64 has strong guarantees on
memory ordering/cache coherency (unless you explicitly use non-temporal
stores).

Andre Kaufmann

unread,
Oct 6, 2010, 7:08:09 AM10/6/10
to
On 04.10.2010 22:30, Vladimir Jovic wrote:
> Andre Kaufmann wrote:
>> On 03.10.2010 09:11, Mathias Gaunard wrote:
>>> On Oct 2, 3:03 pm, Witold Kuzminski<witold.kuzmin...@gmail.com>
>>> wrote:
>>>
>>> [...]
>>> Note that with MSVC, however, volatile does do memory barriers; but
>>> other compilers don't do it, and the C++0x standard, which provides
>>> full support for multithreading, doesn't either.
>>
>> I don't think volatile does do memory barriers in MSVC.
>> It "simply" prevents the compiler from doing register optimizations,
>> which might be relevant for multithreading.
>
> How are volatile relevant for multithreading?
>

It prevents (for example under MSVC) register optimization:

Thread#1: while (!signal);
Thread#2: signal = true

Will commonly fail without marking signal volatile (under MSVC) - since it will
be register optimized (commonly).

Additionally volatile prevents compiler reordering of read/write access (under
MSVC).

Don't know for sure, if it's relevant for other C++ compilers too ?

Andre

Andre Kaufmann

unread,
Oct 6, 2010, 8:03:22 AM10/6/10
to
On 05.10.2010 11:06, Chris M. Thomasson wrote:
> [...]

> I was under the impression that those are compiler barriers. Does
> `_ReadWriteBarrier' actually emit a `MFENCE' or a dummy `XCHG' on an x86? I
> never use them because all of my "critical code" is in externally assembled
> libraries.

Yes, you are correct. It's only a hint for the compiler. The intrinsic
MemoryBarrier, is the one to use to prevent CPU reordering, however under x86 it
will only emit XCHG.
The same applies (regarding documentation) to volatile. It will prevent compiler
reordering of volatile variables - not CPU reordering.

x86 processors do only one kind of reordering, so in most situations only the
compiler reordering is relevant. This changes however if PowerPc and IA64 are
targeted.

It's quite tricky - IMHO - to write multi threaded applications, which run under
multiple CPU platforms correctly. The best option, IMHO is to use critical
sections / mutexes for synchronized access and use the platform specific memory
barrier instructions only where it's (performance) relevant.


Andre

nm...@cam.ac.uk

unread,
Oct 6, 2010, 8:03:20 AM10/6/10
to
In article <i8epta$s33$1...@news.albasani.net>,
Vladimir Jovic <vlada...@gmail.com> wrote:

>Andre Kaufmann wrote:
>>
>> Nethertheless, certain compiler optimizations (register optimizations,
>> reordering) have an impact under multithreaded applications and
>> therefore I think volatile is relevant for such applications, if only to
>> prevent certain compiler optimizations which are hard to track down.
>
>In other words, you are putting volatile to prevent possible buggy
>compiler optimizations.

I wish :-(

My experience with support is that about 85% of such problems in
C are due to ambiguities or inconsistencies in the standard, and
I have seen quite a lot of code that uses volatile precisely to
avoid problems where both the compiler and program could claim
to be compatible with the standard. This is an area where C++
inherits heavily from C and the same will apply, though I have
less experience supporting it.

The killer is that a lot of those problems don't expose themselves
in serial, lightly optimised code, but do with aggressive (though
correct) optimisation or with asynchronicity and parallelism. So
the HPC people (which I am involved with) suffer badly, but most
others don't.

Of course, volatile is almost the worst of all imaginable source
solutions to this problem, but ....


Regards,
Nick Maclaren.

nm...@cam.ac.uk

unread,
Oct 6, 2010, 10:18:54 AM10/6/10
to
In article <i8h0hb$4pv$03$1...@news.t-online.com>,

Well, yes and no. It may be the best of a VERY bad choice, but you
need to do a lot more if you want the programs to be seriously
reliable and portable. There are three main problems:

1) The specifications of the facilities are almost all dire,
and almost every reader will interpret them differently. That,
in itself, doesn't help portability :-(

2) Most of them don't actually specify that they constrain
the compiler and, even if they did, they are at the wrong level.
Other hacks (usually a combination of specific compiler options and
library calls to do things like MFENCE) are needed.

3) Heaven help you when non-memory actions are important,
including locales, signals, the program environment, shared files
and so on. There is rarely ANY way to synchronise those :-(

I am hoping that C++0X will at least focus the minds of the various
organisations, but am not very optimistic. Until and unless there
is significant improvement, using separate processes and message
passing is the only way to write portable or reliable parallel
codes.


Regards,
Nick Maclaren.

Martin Bonner

unread,
Oct 6, 2010, 10:22:29 AM10/6/10
to
On Oct 6, 6:44 am, Vladimir Jovic <vladasp...@gmail.com> wrote:
> memory barriers and fences (this looks like M$ terminology.
> I thought these are called mutexes and semaphores)

No, definitely not MS terminology. Possibly Intel terminology, but I
thought other platforms used something similar.

A mutex says "you won't get the mutex until no other thread has it".
A read memory barrier says "all memory reads before here will complete
before any reads after here".
A write memory barrier says "all memory writes before here will
complete after any writes after here".

You need to realize that to a modern CPU "memory" is a slow peripheral
and reads and writes are usually done in a non-blocking manner.

Jens Schmidt

unread,
Oct 6, 2010, 10:33:04 AM10/6/10
to
Andre Kaufmann wrote:

> On 04.10.2010 22:30, Vladimir Jovic wrote:
>> How are volatile relevant for multithreading?
>>
>
> It prevents (for example under MSVC) register optimization:
>
> Thread#1: while (!signal);
> Thread#2: signal = true
>
> Will commonly fail without marking signal volatile (under MSVC) - since it
> will be register optimized (commonly).
>
> Additionally volatile prevents compiler reordering of read/write access
> (under MSVC).

This only works where bus snooping is implemented. Unfortunately this is
on most machines of the most common architecture. But if your program runs
on a Numa machine, keeping the values out of registers is not enough. They
have to travel through the whole cache hierarchy into main memory for the
other processors to find them.
This travel is initiated by special read and/or write barriers. As it
happens, using such a barrier has the side effect to prevent all non-local
from placement in a register during the call (like for all routines with
unknown accesses to global data).
So in addition to volatile, barriers are necessary. If you use barriers,
volatile is included => volatile is superfluous.

> Don't know for sure, if it's relevant for other C++ compilers too ?

It has the same relevance for other compilers and architectures: none
for multithreading. There are other uses which are still relevant, but
they are of a different kind: hardware access.
--
Greetings,
Jens Schmidt

Message has been deleted

Leigh Johnston

unread,
Oct 6, 2010, 4:07:17 PM10/6/10
to
?"Andre Kaufmann" <akfm...@t-online.de> wrote in message
news:i8h0nm$gf4$02$1...@news.t-online.com...

> On 04.10.2010 22:30, Vladimir Jovic wrote:
>> Andre Kaufmann wrote:
>>> On 03.10.2010 09:11, Mathias Gaunard wrote:
>>>> On Oct 2, 3:03 pm, Witold Kuzminski<witold.kuzmin...@gmail.com>
>>>> wrote:
>>>>
>>>> [...]
>>>> Note that with MSVC, however, volatile does do memory barriers; but
>>>> other compilers don't do it, and the C++0x standard, which provides
>>>> full support for multithreading, doesn't either.
>>>
>>> I don't think volatile does do memory barriers in MSVC.
>>> It "simply" prevents the compiler from doing register optimizations,
>>> which might be relevant for multithreading.
>>
>> How are volatile relevant for multithreading?
>>
>
> It prevents (for example under MSVC) register optimization:
>
> Thread#1: while (!signal);
> Thread#2: signal = true
>
> Will commonly fail without marking signal volatile (under MSVC) - since it
> will be register optimized (commonly).
>
> Additionally volatile prevents compiler reordering of read/write access
> (under MSVC).
>
> Don't know for sure, if it's relevant for other C++ compilers too ?
>

I tried to come up with a contrived example such as the one you give on VC++9
and determined that the optimizer was clever enough such that volatile made no
difference to the assembler output. The mere presence of library function calls
seems to ensure that volatile is not necessary and it is not possible to start a
thread without a library function call. I only spent a couple of hours
investigating this however so there could potentially still be a case where
volatile is useful in VC++ at least (particularly to stop compiler (not CPU)
reordering).

/Leigh

Joshua Maurice

unread,
Oct 6, 2010, 4:36:23 PM10/6/10
to

I don't know how such myths survive despite the community's best
efforts to kill them. Volatile has never been a useful portable multi-
threading primitive in C nor C++, and it almost certainly never will
be. This is because the standards say so, because the standard writers
state that this is their intent, and because most implementations do
not give enough semantics to volatile to make it useful for portable
threading. This will not change because there is code out there (like
the Linux kernel) which relies on current platform specific
definitions of volatile, so strengthening volatile's semantics would
effectively "break" that code by making it much much slower.

If you have some piece of threading code works with volatile but works
without volatile, then it's likely that you got lucky.

Please read:
http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
as the best, most comprehensible description dispelling many of these
common threading myths. It has a whole section devoted to volatile and
why it simply does not work.

If you want to claim volatile is useful for threading, fine - you're
well into platform specific hackery, and you're relying on the
probably non-existent implementation documentation describing
volatile's semantics. That does not make your code standard C++, and
that does not make volatile /portably/ useful for threading.

volatile is there for three things. First, to provide a hook for the
implementation to use for MMIO. However, as James Kanze has said
numerous times on these newsgroups, most modern implementations do not
use that hook. volatile is insufficient to do MMIO on those platforms,
and other platform specific hackery is required. Second, volatile is
useful for accessing variables access setjump longjump pairs. God help
you if you're using those, and I have nothing more to say. That leaves
its third (and generally only legitimately portable) use: variables
used in signal handlers to communicate with the outside program. (Even
then, I bet that most signal handler usage goes well beyond the C and C
++ standards' restrictions on what can be done in signal handlers, and
a fair share even go beyond POSIX's restrictions.)

Leigh Johnston

unread,
Oct 7, 2010, 1:53:55 PM10/7/10
to
?"Leigh Johnston" <le...@i42.co.uk> wrote in message
news:memdnZQOHYiq6jHR...@giganews.com...

My reply was slightly unclear, the following code snippet clarifies it I hope:

static DWORD WINAPI thread_proc(LPVOID aThreadableObject)
{
::Sleep(1000); // wait for a second
bool* p = (bool*)aThreadableObject;
if (p)
*p = false;
return 0;
}

int main()
{
bool gotOne = false;
bool b = false; // not volatile
HANDLE thread = ::CreateThread(NULL, 0, thread_proc, &b, 0, NULL);
b = true;
while(b)
{
gotOne = true;
rand(); /* any (library) function which the optimizer cannot analyze seems to
work,
a wait on the thread or some mutex locking would be less contrived */
}
if (gotOne)
std::cout << "volatile not needed";

Andre Kaufmann

unread,
Oct 8, 2010, 7:25:41 PM10/8/10
to
On 06.10.2010 07:53, tni wrote:
> On 2010-10-04 22:29, Andre Kaufmann wrote:
>> Volatile, as I assume (don't know for sure) in most C++ compilers,
>> prevents certain optimizations. I've checked the generated code for x86
>> processors and the MSVC compiler does not emit any memory barrier code
>> when volatile is used.
>
> It doesn't, because it's not necessary. x86/x64 has strong guarantees on
> memory ordering/cache coherency (unless you explicitly use non-temporal
> stores).

Yes, it has strong guarantees:

- no write reordering occurs (besides some rare exceptions)
- no read reordering occurs

But a x86 CPU is allowed to:

- Reorder reads relative to writes

But anyways, volatile does only prevent compiler optimizations.

Andre

Andre Kaufmann

unread,
Oct 8, 2010, 7:26:01 PM10/8/10
to
On 07.10.2010 19:53, Leigh Johnston wrote:
> ?"Leigh Johnston" <le...@i42.co.uk> wrote in message
> [...]

>> I tried to come up with a contrived example such as the one you give on

The code is only slightly different (have a look at the jump address)
it's hard to see the difference.
I used the following example (MSVC Windows) to check it:

volatile bool flag = false;

DWORD __stdcall SetIt(void* lpThreadParameter)
{
flag = true;
return 0;
}


int main(int argc, char* argv[])
{
QueueUserWorkItem(&SetIt, 0, 0);
while (!flag);
printf("End");
return 0;
}


The assembly code of [while(!flag)] is:

With volatile:

00831020 mov al,byte ptr [aha (833370h)]
00831025 test al,al
00831027 je wmain+10h (831020h)

Without volatile:

008F101F mov al,byte ptr [aha (8F3370h)]
while (!aha);
008F1024 test al,al
008F1026 je wmain+14h (8F1024h)

In the example without volatile, the code jumps to

008F1024 test al,al

and not to

008F101F mov al,byte ptr [aha (8F3370h)]

Since the register is never changed (without volatile) it will be an endless
loop. Simply try to execute the code with and without volatile, you will see the
difference.

Andre

Witold Kuzminski

unread,
Oct 8, 2010, 7:31:35 PM10/8/10
to
On Oct 6, 4:36 pm, Joshua Maurice <joshuamaur...@gmail.com> wrote:

> I don't know how such myths survive

You don't? See the OP.

> despite the community's best efforts to kill them.

Which community do you have in mind?
I believe comp.programming.threads community has done its part by
educating the author about volatile in C and C++.
Java community came up with the DCLP paper, which the page you
provide the link to lists in References.
The C++ community has mostly neglected the problem.
I do understand, that in 2001 multithreaded programming was not
taken seriously by most anybody in C++ community, or else how could
one announce, that their multithreaded code suffers from deadlocks
and still find it appropriate to dispense advice on multithreaded
programming.
Well, it is 2010, and Modern C++ Desing is at its 17-th printing.

I tried to contact the author by email, except most of the links on
his web page are broken, in particular "Input new erratum" is broken,
and the link to his e-mail address is broken.
Moreover, according to author's web page, C++ is not his main research
interest, and the author is "extremely busy with his research", which
I
understand not to be C++ related.
So, in short, it does not look likely, that this thing is going to be
fixed any time soon, and hence my question.

Is it ever going to be fixed? Anyone knows?

Leigh Johnston

unread,
Oct 9, 2010, 4:04:40 PM10/9/10
to

Your version is different to mine as it doesn't involve calling a library
function in the loop. Your version is too contrived as such a busy loop would
be uncommon in practice (as it results in 100% CPU core usage) the common
alternative being the use of synchronization primitives, sleep() etc. I guess
we could argue all day coming up with examples and counter-examples and arguing
if said examples are realistic.

I used to be in the VC++ "use volatile" camp but I have been slowly convinced of
its superfluousness; I still have one volatile in my codebase namely the "state"
variable of my "threadable" base class and I cannot quite bring myself to remove it.

/Leigh

Mathias Gaunard

unread,
Oct 9, 2010, 4:13:23 PM10/9/10
to
On Oct 9, 12:25 am, Andre Kaufmann <akfmn...@t-online.de> wrote:

> But anyways, volatile does only prevent compiler optimizations.

As I already said several times, that's not what MSDN says.
I gave a link that clearly said MSVC introduced CPU memory barries
since version 8.

Andre Kaufmann

unread,
Oct 10, 2010, 5:04:59 PM10/10/10
to
On 09.10.2010 22:04, Leigh Johnston wrote:
> [...]

> Your version is different to mine as it doesn't involve calling a
> library function in the loop. Your version is too contrived as such a
> busy loop would be uncommon in practice (as it results in 100% CPU core
> usage)

Yes agreed, I wouldn't write it that way either in normal applications. It
should be only an example that volatile has an effect (on a dumb program here).
If you have a look at old implementations of Win32-InterlockedIncrement you will
recognize that "volatile" has been added to the argument in newer header files.

Volatile prevents several optimizations (under MSVC at least) - including
register optimization and compiler read/write reordering.
May be that there are very rare cases where it's relevant (double checked
locking etc.) and that there are even fewer cases where register optimization
might bite one (e.g. if the compiler calls functions, where it has the complete
overview).
I use volatile for variables that are not protected by mutexes (lock free) just
to indicate, that the variable is modified by several threads. And since I use
frequently Interlocked.... functions under Windows I'm forced to use volatile
for my variables anyways, since I don't want to use always const_casts to get
rid of volatile.

With this scheme you even can use volatile (same as const for const correctness)
for thread safety/correctness.

E.g.:


class Thread
{
public:

void Operation() volatile { printf("thread safe\r\n"); }
void Operation() { printf("fast\r\n"); }
};


int main(int argc, char* argv[])
{

volatile Thread t1;
Thread t2;

t1.Operation(); // Calls the volatile function -
// thread safe implementation
t2.Operation(); // Calls the non-volatile function - fast
// but not thread safe

return 0;
}


> [...]
Andre

Dragan Milenkovic

unread,
Oct 12, 2010, 1:06:47 AM10/12/10
to
On 10/10/2010 11:04 PM, Andre Kaufmann wrote:
[snip]

> With this scheme you even can use volatile (same as const for const
> correctness) for thread safety/correctness.

If I may notice -- you cannot use it for correctness, and certainly
not the same as "const" for const correctness. You only use it
to _mark_ your functions and objects as thread-safe instead
of using a different name. There is no real check from the compiler
concerning thread-safety itself.

Furthermore, you don't even mark _functions_ but only _objects_
as thread-safe. Can that really help for other than trivial cases?
I'd rather name functions differently or provide different interfaces,
I'm not able to give a general argument here.

Not a very smart example, but let me try:

// good code

struct Vector {
int get_last() volatile;
void remove_last() volatile;
};

// bad code; user has a false sense of security from "volatile"

int pop(volatile Vector & v) {
int res = v.back();
v.remove_last();
return res;
}

So, the user of volatile has to use brains to write the correct
code, but in doing so, does he really need "volatile" other
than to mark the thread-safety intention? For example, prefix
everything with "ts_" and you lose very little, IMHO.

In the same way, "volatile" can be used for "optimized
but not heavily tested" function versions. What I'm trying
to say is that it's not really related to thread-safety.

Additional argument: Speaking generally, from the outside, the same
object can be used in a thread safe manner on in an unsafe manner.
The object itself doesn't need to know and even cannot handle how
it's going to be used, so making an object "volatile",
to me and in my humble opinion, doesn't have a deeper meaning.

--
Dragan

Joshua Maurice

unread,
Oct 12, 2010, 1:05:59 AM10/12/10
to
On Oct 5, 10:44 pm, Vladimir Jovic <vladasp...@gmail.com> wrote:
> 3) What about the programs that are not multi-threaded? They do not need
> to worry about such problems, and they certainly don't need memory
> barriers and fences (this looks like M$ terminology. I thought these are
> called mutexes and semaphores)

These terms are not microsoft-isms. Mutexes and semaphores are very
different things than memory barriers and fences. As a simple
characterization, mutexes and semaphores have blocking semantics.
Memory barriers / fences do not ever block the calling thread. It
seems you are unknowledgeable about low level threading details. I
would suggest reading some or all of the following resources, which I
whipped out off the top of my head and with some quick googling.

Most relevantly, we have numerous papers discussing the C++0x memory
model.
http://www.hpl.hp.com/techreports/2008/HPL-2008-56.pdf
http://www.cl.cam.ac.uk/~pes20/cpp/tech.pdf
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2008/n2664.htm
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2338.html

We also have the Linux kernel documentation, which describes what C+
+0x calls std::memory_order_acquire, std::memory_order_release, and
std::memory_order_consume.
http://www.mjmwired.net/kernel/Documentation/memory-barriers.txt

Finally, the JSR-133 Cookbook for Compiler Writers is another useful
discussion of memory barriers. It's useful, though it's in the context
of Java.
http://g.oswego.edu/dl/jmm/cookbook.html

Andrei Alexandrescu

unread,
Oct 12, 2010, 1:07:53 AM10/12/10
to

The threading-related stuff in Modern C++ Design has aged badly and contains
statements about "volatile" that were untrue even in 2001.

At this point fixing the 18th printing book will not fix the copies already sold
and will not effectively dispel possible misconceptions. You may want to point
out anyone who uses the volatile-related quote in the book as reference to the
article http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf which (a) is
coauthored by Modern C++ Design's author, (b) is more recent than the book, and
(c) settles the matter once and for all.


Andrei

P.S. Sorry, I didn't receive email from you.

Witold Kuzminski

unread,
Oct 12, 2010, 7:33:53 PM10/12/10
to
On Oct 10, 5:04 pm, Andre Kaufmann <akfmn...@t-online.de> wrote:
> With this scheme you even can use volatile (same as const for const correctness)
> for thread safety/correctness.

Proper use of const type qualifier provides for const correctness of
the code.
Using volatile type qualifier is not going to make your code thread
safe.

Andre Kaufmann

unread,
Oct 12, 2010, 7:51:34 PM10/12/10
to
On 12.10.2010 07:06, Dragan Milenkovic wrote:
> On 10/10/2010 11:04 PM, Andre Kaufmann wrote:
> [snip]
>> With this scheme you even can use volatile (same as const for const
>> correctness) for thread safety/correctness.
>
> If I may notice -- you cannot use it for correctness, and certainly
> not the same as "const" for const correctness. You only use it

Sure - only for marking functions.
I don't know any language which supports true "thread correctness", besides
implicit correctness like functional languages, which have immutable objects
where some typical multithreading problems simply can't occur, since objects are
not modified by multiple threads.

> In the same way, "volatile" can be used for "optimized
> but not heavily tested" function versions. What I'm trying
> to say is that it's not really related to thread-safety.

Same as const is not related to >true< const correctness either - IMHO.
You can simply cast it away and it's only checked compilation level.
True protection would be to lock and protect the objects on CPU level too.

If you don't protect your objects with const or if you have to cast it away,
because some functions you call don't accept const objects these functions can
modify the normally protected objects and the compiler doesn't complain about.

> Additional argument: Speaking generally, from the outside, the same
> object can be used in a thread safe manner on in an unsafe manner.
> The object itself doesn't need to know and even cannot handle how
> it's going to be used, so making an object "volatile",
> to me and in my humble opinion, doesn't have a deeper meaning.

It only can help you protect objects to be modified by functions, which are not
thread safe, e.g. if you enter a critical section in a volatile function /
object and you pass it on to other functions, you can be sure that this object
is protected if you follow this scheme in the whole code. It doesn't surely
protect you from deadlocks or from "not thread safe" modifications anyway.

It's just might help you to separate thread safe paths from "not thread safe" ones.

But I agree, it's only a "small help" regarding the many problems, which can
occur, where volatile can't help to detect the problems on compilation.

So I think we both agree on the term (?):

"volatile may help to write multithreaded applications and protect from some
problems at compilation level" - it doesn't ensure "thread correctness" and can't.

Andre

--

Andre Kaufmann

unread,
Oct 13, 2010, 2:51:35 AM10/13/10
to
On 13.10.2010 01:33, Witold Kuzminski wrote:
> On Oct 10, 5:04 pm, Andre Kaufmann<akfmn...@t-online.de> wrote:
>> With this scheme you even can use volatile (same as const for const correctness)
>> for thread safety/correctness.
>
> Proper use of const type qualifier provides for const correctness of
> the code.
> Using volatile type qualifier is not going to make your code thread
> safe.

As I already mentioned in another post, it can help you only to separate
(hopefully) thread safe code from other code.
I doesn't ensure thread safety - for sure - how could it.

Andre

Witold Kuzminski

unread,
Oct 15, 2010, 1:15:51 AM10/15/10
to
On Oct 13, 2:51 am, Andre Kaufmann <akfmn...@t-online.de> wrote:
> >> With this scheme you even can use volatile [...]
> >> for thread safety/correctness.

>
> As I already mentioned in another post, it can help you only to separate
> (hopefully) thread safe code from other code.

Well, it was not quite what you said.
Now, why would I need help to (hopefully) separate thread safe, from
other code?

> I doesn't ensure thread safety - for sure - how could it.

Right.
Also, I find your example rather odd.
[...]


void Operation() volatile { printf("thread safe\r\n"); }
void Operation() { printf("fast\r\n"); }

[...]


t1.Operation(); // Calls the volatile function -
// thread safe implementation
t2.Operation(); // Calls the non-volatile function - fast
// but not thread safe

You say, that t2.Operation() is fast.
The t1.Operation() is then presumably slow.
>From that I understand, that in order to run fast, you want to run
single-threaded, yes?

Andre Kaufmann

unread,
Oct 15, 2010, 8:10:49 AM10/15/10
to
On 15.10.2010 07:15, Witold Kuzminski wrote:
> On Oct 13, 2:51 am, Andre Kaufmann<akfmn...@t-online.de> wrote:
>>>> With this scheme you even can use volatile [...]
>>>> for thread safety/correctness.
>>
>> As I already mentioned in another post, it can help you only to separate
>> (hopefully) thread safe code from other code.
>
> Well, it was not quite what you said.

I you want to be pedantic on the word correctness, then yes I didn't
write that. I wrote "you can use volatile for thread
safety/correctness", which shouldn't mean "volatile ensures thread
safety/correctness 100%".

> Now, why would I need help to (hopefully) separate thread safe, from
> other code?

Why do I want to use "const" ? To protect memory to be "accidentally" be
written to.

The same can be done with volatile too. You must follow a scheme - e.g.
that volatile functions must only be called if the object is initially
protected by a mutex.

E.g.:

volatile Object& GetThreadSafe() { EnterMutex(); return object; }
Object& GetUnsafe () { return object; }

If you only modify not thread safe member variables (lists etc.) in
volatile functions, you can only call them if you use GetThreadSafe() to
access the object. Otherwise the compiler will complain.
If the functions itself are not thread safe it doesn't help to write
thread safe code.

I rather prefer to synchronize the object internally, but sometimes
external synchronization is needed and there volatile "might" help.


>> I doesn't ensure thread safety - for sure - how could it.
>
> Right.
> Also, I find your example rather odd.

I though a long example is not necessary to get the idea.

> [...]


> You say, that t2.Operation() is fast.
> The t1.Operation() is then presumably slow.
>> From that I understand, that in order to run fast, you want to run
> single-threaded, yes?

No - but not synchronized / locked by a mutex. It's a difference if
multiple threads access an object by entering a "global" mutex, calling
then different long running functions, which could be executed in
parallel, instead of all threads waiting for the mutex to be released.

Andre

Witold Kuzminski

unread,
Oct 15, 2010, 5:19:40 PM10/15/10
to
On Oct 12, 1:07 am, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
>
> > Is it ever going to be fixed? Anyone knows?
>
> The threading-related stuff in Modern C++ Design has aged badly and contains
> statements about "volatile" that were untrue even in 2001.

Same can be said about DCLP.

> At this point fixing the 18th printing book will not fix the copies already sold
> and will not effectively dispel possible misconceptions.

It would not fix the copies already sold, but it would help dispel
possible
misconceptions by containing the damage.
Do you mean you are not going to fix it?

> You may want to point
> out anyone who uses the volatile-related quote in the book as reference to the

> articlehttp://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdfwhich (a) is


> coauthored by Modern C++ Design's author, (b) is more recent than the book, and
> (c) settles the matter once and for all.

How does it settle the matter?
Presented with two contradicting surces of information:
1. something "on the web" from Jul/Aug 2004
2. the book printed in Feb 2009
I'd check the book errata. Finding nothing in the arrata I'd say the
book was
more reliable source of information.
What would you say?

> P.S. Sorry, I didn't receive email from you.

I could not find your e-mail address.
Here is what the link (from your web page) to your e-mail address
brings:
http://web.archive.org/web/20061007145400/cooltext.com/

Dragan Milenkovic

unread,
Oct 15, 2010, 8:24:44 PM10/15/10
to

But the modern C++ design (as in what most of the programmers
write today) suffers from many wrongdoings, and I'd say that
your objections are the least of the horrors that can be found
in the code being written just as we speak... I haven't read
the book, but could it be that it tells about this modern code?

Seriously though... Shouldn't the book or errata mention
those documents instead of relying on people to point
other people to online resources? Either that or
the book should be dubbed obsolete, possibly replaced
by "The _real_ modern C++ design" or something...

--
Dragan

Witold Kuzminski

unread,
Oct 16, 2010, 4:11:42 PM10/16/10
to
On Oct 15, 8:10 am, Andre Kaufmann <akfmn...@t-online.de> wrote:
> On 15.10.2010 07:15, Witold Kuzminski wrote:

> I wrote "you can use volatile for thread
> safety/correctness", which shouldn't mean "volatile ensures thread
> safety/correctness 100%".

I thought, that correctness was a binary property: something is
either correct, or it is not.
Considering you did not mean "volatile ensures thread
safety/correctness 100%", it would be logical to conclude, that
you meant volatile ensures no thread safety/correctness, and
consequently, you should have said, that "you must not use
volatile for thread safety/correctness", instead of what you
did say.

> > Now, why would I need help to (hopefully) separate thread safe, from
> > other code?
>
> Why do I want to use "const" ? To protect memory to be "accidentally" be
> written to.

I do not want to use const for that. I try to avoid "accidents"
instead.

> The same can be done with volatile too.

Not sure what exactly do you mean saying "the same", but since I asked
about separating thread-safe from other code, I suspect that, to be
what
you meant.
Well, I never needed to separate thread-safe from the other code,
because the two do not mix well. I can wholeheartedly recommend this
approach - do not mix thread-safe with other code, and you are never
going to need help separating one from the other.

> You must follow a scheme - e.g.

Your "scheme" looks vaguely familiar. Is it, by any chance, based
on this: http://www.drdobbs.com/184403766 ?

Francis Glassborow

unread,
Oct 16, 2010, 4:30:14 PM10/16/10
to
On 16/10/2010 01:24, Dragan Milenkovic wrote:

>
> But the modern C++ design (as in what most of the programmers
> write today) suffers from many wrongdoings, and I'd say that
> your objections are the least of the horrors that can be found
> in the code being written just as we speak... I haven't read
> the book, but could it be that it tells about this modern code?
>
> Seriously though... Shouldn't the book or errata mention
> those documents instead of relying on people to point
> other people to online resources? Either that or
> the book should be dubbed obsolete, possibly replaced
> by "The _real_ modern C++ design" or something...
>

Readers of technical books MUST get into the habit of checking the copyright
date for the edition they have. Books may be reprinted, and these days that
usually means that there will be some changes as a result of errors that have
been spotted but it is only the copyright date of the edition (not any reprint)
that is relevant.

I have many books on my shelves that are obsolete in the sense that they were
written in an earlier era and so technical changes may mean that they are no
longer accurate, that does not make them automatically obsolete as sources of
ideas and insights. Ruminations on C++ by Andy Koenig, C Traps and Pitfalls by
the same author, the Design and Evolution of C++ by Bjarne Stroustrup etc. are
books that remain worth their shelf space and the time taken reading them but
there will be things in them that have been superseded.

I chose those examples exactly because I know the authors have no current
intention of producing a new edition even though much has changed since they
were written.

It is part of good study habits to know when a book was published. Over 50 years
ago during an interview to read a Mathematics degree at Merton College, Oxford I
was asked for the date of publication of a book on mathematical philosophy I had
listed among the titles that I had read. You might think that was an
unreasonable question. It wasn't, the second edition of the book was radically
different to the first. I guess the interviewing panel was a little bit
surprised when I quoted not only the year but the month as well (actually I was
one month out :) ). But they were right to ask because my answers to other
questions needed to be judged by which edition I had read.

I am sure that were Andre Alexandrescu to write 'Modern C++ Design' today that
he would write a substantially different book. Nonetheless his book was a ground
breaker when it was published and contains many valuable ideas that deserve to
be more widely known. However it is not and never was an authoritative work on
how to write thread safe software and any reader who thinks that any part of it
was is, IMO, being unreasonable.

--

Andre Kaufmann

unread,
Oct 17, 2010, 10:09:57 PM10/17/10
to
On 16.10.2010 22:11, Witold Kuzminski wrote:
> On Oct 15, 8:10 am, Andre Kaufmann<akfmn...@t-online.de> wrote:
>>[...]

>> Why do I want to use "const" ? To protect memory to be "accidentally" be
>> written to.
>
> I do not want to use const for that. I try to avoid "accidents"
> instead.

Hm, what does const then do, besides to prevent write access/general
modifications. Generally speaking, to separate write from read functions ?

>> The same can be done with volatile too.
>
> Not sure what exactly do you mean saying "the same", but since I asked
> about separating thread-safe from other code, I suspect that, to be
> what
> you meant.
> Well, I never needed to separate thread-safe from the other code,

I too try to make the whole object thread safe, instead of only some methods.
Honestly I don't need volatile and wouldn't recommend it, I use other schemes.
Where it perhaps might be needed is if e.g. a list has to be protected by a
mutex, when iterated. Stepping through the list would be allowed (volatile
methods), while modifications calls would be protected (non volatile methods).

Instead of volatile to protect list modifications I rather would use the visitor
pattern in combination with C++0x lambda functions.

Where volatile would be really helpful is if it would be more thread specific.
E.g. if I could specify that a function/object is and must be protected by a
specific mutex.

The compiler would then be able to:

- detect unprotected access
- detect deadlocks

Such tracing is commonly done by tools at runtime, to detect deadlocks. With
some new keywords the compiler could detect such problems at compilation time.

E.g.: (Pseudo code)

void Foo1() volatile_needs MyMutex1 // Marks function to need MyMutex1
void Foo2() volatile_needs MyMutex2

void Foo()
{
// Mark function call to enter MyMutex1
myMutex1.Enter() volatile_enter MyMutex1;

Foo1(); // O.k.
Foo2(); // Fails

myMutex1.Leave() volatile_leave MyMutex1;
}


>> BUT <<:

Realistically I think the compiler is unable to track all the calls, so
introducing such new keywords wouldn't make no sense at all, since all function
calls would then have to be marked to be safe manually.

> [...]


>> You must follow a scheme - e.g.
>
> Your "scheme" looks vaguely familiar. Is it, by any chance, based
> on this: http://www.drdobbs.com/184403766 ?

There are several articles like the one you mentioned.
I have read many of them, but don't refer to a specific one.

But to make a long story short:

Volatile could be used to help to write thread safe code, but IMHO there are
(nowadays) better patterns/schemes and I wouldn't recommend volatile anyways to
be used in this way.


Andre

Witold Kuzminski

unread,
Oct 17, 2010, 10:12:01 PM10/17/10
to
On Oct 15, 8:24 pm, Dragan Milenkovic <dra...@plusplus.rs> wrote:
> On 10/15/2010 11:19 PM, Witold Kuzminski wrote:

> But the modern C++ design (as in what most of the programmers
> write today) suffers from many wrongdoings, and I'd say that

What specific wrongdoings do you have in mind?

> your objections are the least of the horrors that can be found
> in the code being written just as we speak...

The least of the horrors? No. You need to look no further than
this thread to see it clearly.

> I haven't read
> the book, but could it be that it tells about this modern code?

No. No way it tells about that.
You would not need to ask, had you actually read the book.

>
> Seriously though... Shouldn't the book or errata mention
> those documents instead of relying on people to point
> other people to online resources?

Might be a good idea. What do you think?

--

Witold Kuzminski

unread,
Oct 18, 2010, 1:43:27 AM10/18/10
to
On Oct 17, 10:09 pm, Andre Kaufmann <akfmn...@t-online.de> wrote:
> On 16.10.2010 22:11, Witold Kuzminski wrote:

> >> You must follow a scheme - e.g.
>
> > Your "scheme" looks vaguely familiar. Is it, by any chance, based
> > on this:http://www.drdobbs.com/184403766?
>
> There are several articles like the one you mentioned.

I must say, that I did not know. Are you sure?

> I have read many of them

Any references would be greatly appreciated.
Thanks.

Andre Kaufmann

unread,
Oct 18, 2010, 5:26:34 PM10/18/10
to
On 18.10.2010 07:43, Witold Kuzminski wrote:
> On Oct 17, 10:09 pm, Andre Kaufmann<akfmn...@t-online.de> wrote:
>> On 16.10.2010 22:11, Witold Kuzminski wrote:
>
>>>> You must follow a scheme - e.g.
>>
>>> Your "scheme" looks vaguely familiar. Is it, by any chance, based
>>> on this:http://www.drdobbs.com/184403766?
>>
>> There are several articles like the one you mentioned.
>
> I must say, that I did not know. Are you sure?

Yes, since I don't remember the content of the one (from DDJ) you mentioned that
much. It was a different example and different author. At least I remember 2
additional articles. (I would have remembered it, if they would have been
written by A. Alexandrescu).

>> I have read many of them
>
> Any references would be greatly appreciated.
> Thanks.

I tried (hard) to find them online - to prevent the appearance of excuse, but
failed.
One of them I remember was IIRC in a non English magazine, which is commonly not
(freely) available online and since there are too many hits for volatile it's
hard to find any of them, if they aren't indexed (fully) by a search engines robot.

May be that the articles were based on the DDJ one and have just used different
examples. At least they were based on the same idea and IIRC they must have been
written some years after the DDJ article.

Since then no other articles have been published online, I suspect the idea has
not been adopted for C++ development that much.

Andre

Dragan Milenkovic

unread,
Oct 18, 2010, 9:46:03 PM10/18/10
to

What you say makes perfect sense. And in no way am I trying to make
people's contributions throughout time look less important than
they obviously are. And apparently, I have used a bad term "obsolete".
The issue here, unless I'm completely off the track, is that some
things were simply wrong, not that they became obsolete... some of
them, not the entire book. I was thinking that if it were my book,
I would make relevant changes, or pass on to someone else to do it
in case I don't have time. But, I understand that this is very arrogant
for me to state since I haven't written any books. Just a thought...

--
Dragan

Andrei Alexandrescu

unread,
Oct 18, 2010, 9:53:07 PM10/18/10
to
On 10/15/2010 04:19 PM, Witold Kuzminski wrote:
> On Oct 12, 1:07 am, Andrei Alexandrescu
> <SeeWebsiteForEm...@erdani.org> wrote:
>>
>>> Is it ever going to be fixed? Anyone knows?
>>
>> The threading-related stuff in Modern C++ Design has aged badly and
>> contains statements about "volatile" that were untrue even in
>> 2001.
>
> Same can be said about DCLP.

Yes and no. In the real world DCLP is used in code of various degrees of
non-portability that needs to meet certain performance requirements.

>> At this point fixing the 18th printing book will not fix the
>> copies already sold and will not effectively dispel possible
>> misconceptions.
>
> It would not fix the copies already sold, but it would help dispel
> possible misconceptions by containing the damage. Do you mean you
> are not going to fix it?

I have contacted my editor to address the matter. I doubt it would have
a measurable effect though, but it's good to set the record straight.
Thanks for bringing the issue to my attention.

>> You may want to point out anyone who uses the volatile-related
>> quote in the book as reference to the article
>> http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf which
>> (a) is coauthored by Modern C++ Design's author, (b) is more
>> recent than the book, and (c) settles the matter once and for all.
>
> How does it settle the matter? Presented with two contradicting
> surces of information: 1. something "on the web" from Jul/Aug 2004
> 2. the book printed in Feb 2009 I'd check the book errata. Finding
> nothing in the arrata I'd say the book was more reliable source of
> information. What would you say?

Many things are found on the Web. That doesn't make it wholesale less
credible than printed matter.

After having fixed a lot of errata in printing 13, I have received no
more submissions for years. Since recently I have moved my web server to
a different host, which does not support the database I used for the
errata. For my new book I decided to use a wiki setup, and so the errata
submission flow for Modern C++ Design has been neglected.

>> P.S. Sorry, I didn't receive email from you.
>
> I could not find your e-mail address. Here is what the link (from
> your web page) to your e-mail address brings:
> http://web.archive.org/web/20061007145400/cooltext.com/

My email address is, and has been for a very long time, available at
http://erdani.com/email, rendered in graphics to dodge bots. That page
is the first hit for google queries like 'email andrei alexandrescu',
and is linked from my homepage (click on "Email"). The link you point to is a
link to the program that I used back then to convert my email address to an image.

I infer from the tone of some of your posts that you are aggravated by
this issue above and beyond the level of a reader who finds an erratum
in a book. If there's anything I can do to help that, please let me know.


Thanks,

Andrei

Öö Tiib

unread,
Oct 19, 2010, 4:54:49 AM10/19/10
to
On 15 okt, 15:10, Andre Kaufmann <akfmn...@t-online.de> wrote:
>
> The same can be done with volatile too. You must follow a scheme - e.g.
> that volatile functions must only be called if the object is initially
> protected by a mutex.
>
> E.g.:
>
> volatile Object& GetThreadSafe() { EnterMutex(); return object; }
> Object& GetUnsafe () { return object; }
>
> If you only modify not thread safe member variables (lists etc.) in
> volatile functions, you can only call them if you use GetThreadSafe() to
> access the object. Otherwise the compiler will complain.
> If the functions itself are not thread safe it doesn't help to write
> thread safe code.

I read and wondered and now i strongly suspect that what you meant
here is exactly other way around. volatile qualifier behaves like
const qualifier for classes. So you can call volatile members of your
non-volatile object but you get compiling errors for calling non-
volatile members of volatile object. Also you can take or pass by
volatile references non-volatile objects but not other way around.

Therefore volatile can be considered thread-unsafe and non-volatile
can be considered thread safe. Otherwise you can call thread safe
members of your unsafe object and "the scheme" does not work at all.

As for the scheme ... it perhaps improves performance of non-shared
objects, but it also feels like lot of bloat code in class. Container
with 4 operator[]() overloads for example. Also what do the volatile
versions do? scoped lock and const_cast<T*>(this)-
>non_volatile_overload(params); ? It sounds like even more ways to get
into deadlocks.

Witold Kuzminski

unread,
Oct 20, 2010, 4:30:08 AM10/20/10
to
On Oct 18, 9:53 pm, Andrei Alexandrescu

<SeeWebsiteForEm...@erdani.org> wrote:
> On 10/15/2010 04:19 PM, Witold Kuzminski wrote:

> >> The threading-related stuff in Modern C++ Design has aged badly and
> >> contains statements about "volatile" that were untrue even in
> >> 2001.
>
> > Same can be said about DCLP.
>
> Yes and no.

Please see the quote from the article you provided the link to,
and which you co-authored; it is from the introduction:
"This article explains why Singleton isn�t thread safe, how DCLP
attempts to address that problem, why DCLP may fail on both
uni- and multiprocessor architectures, and why you can�t
(portably) do anything about it."

> In the real world DCLP is used in code of various degrees of
> non-portability that needs to meet certain performance requirements.

Many incorrect or inadequate solutions could likely be made to "work"
under some special circumstances.

> I have contacted my editor to address the matter. I doubt it would have
> a measurable effect though,

You might not fully realize how popular your book is.

> Thanks for bringing the issue to my attention.

You are welcome. Thanks for taking the time to fix it.

Charles Bryant

unread,
Oct 20, 2010, 11:49:34 PM10/20/10
to
In article <ZZOdnY5hF_pwUjHR...@giganews.com>,
Leigh Johnston <le...@i42.co.uk> wrote:
}My reply was slightly unclear, the following code snippet clarifies it I hope:
}
}static DWORD WINAPI thread_proc(LPVOID aThreadableObject)
}{
} ::Sleep(1000); // wait for a second
} bool* p = (bool*)aThreadableObject;
} if (p)
} *p = false;
} return 0;
}}
}
}int main()
}{
} bool gotOne = false;
} bool b = false; // not volatile
} HANDLE thread = ::CreateThread(NULL, 0, thread_proc, &b, 0, NULL);
} b = true;
} while(b)
} {
} gotOne = true;
} rand(); /* any (library) function which the optimizer cannot analyze seems to
}work,
} a wait on the thread or some mutex locking would be less contrived */
} }
} if (gotOne)
} std::cout << "volatile not needed";
}}

In case anyone is wondering, no compiler can optimise the test of b.
If the compiler does not know what ::CreateThread() does, then it must
allow for the possibility that it saves the address of b that was
passed in. If it does not know what rand() does, then it must allow
for the possibility that it uses the pointer saved by ::CreateThread()
to change b. Hence it cannot optimise away the repeated test of b.

Of course a compiler might be smart enough to know what those function
do, but in that case it would know that the address of b was likely to
be used by another thread and also could not optimise away the test of
b.

If a compiler was smart enough to know that rand() couldn't modify b
but not smart enough to know that ::CreateThread() created a thread,
which could modify b then it is an excellent example of the proverb that
"A little knowledge is a dangerous thing." because such a compiler is
useless for multithreaded code.

Miles Bader

unread,
Oct 23, 2010, 2:32:23 PM10/23/10
to
Witold Kuzminski <witold.k...@gmail.com> writes:
> Please see the quote from the article you provided the link to,
> and which you co-authored; it is from the introduction:
> "This article explains why Singleton isn’t thread safe, how DCLP

> attempts to address that problem, why DCLP may fail on both
> uni- and multiprocessor architectures, and why you can’t

> (portably) do anything about it."

How can one do something about it _non-portably_?

E.g., if the single-time initialization of `shared_field' (only called
when shared_field == NULL) looks like:

{
LockGuard guard (lock);

if (! shared_field)
{
local_variable = make_expensive_data_structure ();
MEMORY_BARRIER ();
shared_field = local_variable;
}
}

where "MEMORY_BARRIER" is __sync_synchronize or equivalent (both acts
as a compiler memory-barrier and emits an appropriate CPU-specific
memory-barrier instruction or whatever).

Then I guess one could encapsulate that in a class that used the
efficient technique on systems where some MEMORY_BARRIER was available
(90% ?), and an explicit lock or something on anything else.

Thanks,

-miles

--
`...the Soviet Union was sliding in to an economic collapse so comprehensive
that in the end its factories produced not goods but bads: finished products
less valuable than the raw materials they were made from.' [The Economist]

Andre Kaufmann

unread,
Oct 23, 2010, 2:36:01 PM10/23/10
to
On 19.10.2010 10:54, �� Tiib wrote:
> On 15 okt, 15:10, Andre Kaufmann<akfmn...@t-online.de> wrote:
>>
> [...]

> I read and wondered and now i strongly suspect that what you meant
> here is exactly other way around. volatile qualifier behaves like
> const qualifier for classes. So you can call volatile members of your
> non-volatile object but you get compiling errors for calling non-
> volatile members of volatile object. Also you can take or pass by
> volatile references non-volatile objects but not other way around.
>

If I understood you correctly, yes you are right if you use volatile
this way.

I rather used (now I don't use it anymore) it to

a) "Automatically" call the appropriate function if the object supported
external and internal synchronization

A small (quick hacked) sample may illustrate this:


#define assert // Just for illustration

// Mutex class - illustrative implementation
class Mutex
{
public: void Lock(){} void Unlock() {}
bool IsLocked() volatile const { return false; }
}

;
class Thread
{
public:

// Externally protected - Lock externally called
void Add(int value) volatile
{
assert(mutex.IsLocked()); // Check at runtime too
printf("External\r\n");
const_cast<std::vector<int>&>(list).push_back(value);
}

// Internally protected
void Add(int value)
{
printf("Internal\r\n");
mutex.Lock();
list.push_back(value);
mutex.Unlock();
}

void Lock() { mutex.Lock(); }

private:
std::vector<int> list;
Mutex mutex;
};

class ThreadHolder
{
public:

volatile Thread& Lock () { thread.Lock(); return thread; }
Thread& Unprotected () { return thread; }

private:
Thread thread;
};


int main(int argc, char* argv[])
{

ThreadHolder f;

f.Lock().Add(10); // Prints: "External"
f.Unprotected().Add(29); // Prints: "Internal"

return 0;
}

Depending if the object is accessed by Lock() it returns either a
volatile one or one without volatile.
All functions called on the object returned by ThreadHolder.Lock(), will
be assumed to be externally synchronized.

In this case both variants are thread safe. But the locking scheme is
different. Function based and externally synchronized.

The unsafe case, if the volatile function is called, but the mutex isn't
locked will be only checked at runtime (may be it can be checked at
compilation time using meta templates)

> [...]

As I already wrote, I don't use volatile that often anymore:

a) Functors/Lambdas reduce the cases where external
synchronization is necessary - visitor pattern

b) I don't like const_cast to be used that often, mutable doesn't apply
to volatile (IIRC)

c) It doesn't help to protect against typical
multithread errors like deadlocks anyways

Andre

Joshua Maurice

unread,
Oct 23, 2010, 2:35:17 PM10/23/10
to
On Oct 20, 8:49 pm, Charles Bryant <n871108125...@chch.demon.co.uk>
wrote:
> In article <ZZOdnY5hF_pwUjHRnZ2dnUVZ8uCdn...@giganews.com>,

What? No. That is a perfectly legitimate compiler transformation. You
have a formal race condition, so all bets are off. If you want
communication between threads, that communication /must/ be with
proper synchronization primitives. Otherwise you're in undefined
behavior land.

I don't recall what the win32 spec says on the matter, but I suspect
it's undefined behavior. IIRC POSIX says this program (with the
pthreads primitives) has a race condition, and race conditions are
undefined behavior. Finally, C++0x definitely says that this program
(with the C++0x primitives) would have a race condition, and race
conditions are undefined behavior.

Joshua Maurice

unread,
Oct 24, 2010, 8:27:35 PM10/24/10
to
On Oct 23, 11:32 am, Miles Bader <mi...@gnu.org> wrote:

> Witold Kuzminski <witold.kuzmin...@gmail.com> writes:
> > Please see the quote from the article you provided the link to,
> > and which you co-authored; it is from the introduction:
> > "This article explains why Singleton isn�t thread safe, how DCLP

> > attempts to address that problem, why DCLP may fail on both
> > uni- and multiprocessor architectures, and why you can�t

> > (portably) do anything about it."
>
> How can one do something about it _non-portably_?
>
> E.g., if the single-time initialization of `shared_field' (only called
> when shared_field == NULL) looks like:
>
> {
> LockGuard guard (lock);
>
> if (! shared_field)
> {
> local_variable = make_expensive_data_structure ();
> MEMORY_BARRIER ();
> shared_field = local_variable;
> }
> }
>
> where "MEMORY_BARRIER" is __sync_synchronize or equivalent (both acts
> as a compiler memory-barrier and emits an appropriate CPU-specific
> memory-barrier instruction or whatever).
>
> Then I guess one could encapsulate that in a class that used the
> efficient technique on systems where some MEMORY_BARRIER was available
> (90% ?), and an explicit lock or something on anything else.
>
> Thanks,

I would count that as portable. Any hardware that has multiple cores
offers memory barriers in some form or another. The trick is then to
coerce the compiler, linker, et al, to not mess with it, and if your
compiler doesn't already understand threading, this is probably an
exercise doomed to fail. The compiler must understand threading issues
to some degree, otherwise it can and will generate thread-unsafe
code.

I think the quote meant to say "And why you can't do anything about it
portably /and reliably in pure C++/." As soon as you go outside pure C+
+03, such as to POSIX, win32, C++0x, a library which you implemented
with assembly for each platform, Boost, etc., then you can do stuff
about it portably.

--

Andy Venikov

unread,
Oct 26, 2010, 3:11:34 AM10/26/10
to
On 10/23/2010 2:32 PM, Miles Bader wrote:
> Witold Kuzminski<witold.k...@gmail.com> writes:
>> Please see the quote from the article you provided the link to,
>> and which you co-authored; it is from the introduction:
>> "This article explains why Singleton isn’t thread safe, how DCLP
>> attempts to address that problem, why DCLP may fail on both
>> uni- and multiprocessor architectures, and why you can’t
>> (portably) do anything about it."
>
> How can one do something about it _non-portably_?
>
> E.g., if the single-time initialization of `shared_field' (only called
> when shared_field == NULL) looks like:
>
> {
> LockGuard guard (lock);
>
> if (! shared_field)
> {
> local_variable = make_expensive_data_structure ();
> MEMORY_BARRIER ();
> shared_field = local_variable;
> }
> }
>
> where "MEMORY_BARRIER" is __sync_synchronize or equivalent (both acts
> as a compiler memory-barrier and emits an appropriate CPU-specific
> memory-barrier instruction or whatever).
>
> Then I guess one could encapsulate that in a class that used the
> efficient technique on systems where some MEMORY_BARRIER was available
> (90% ?), and an explicit lock or something on anything else.
>
> Thanks,
>
> -miles

I'm sorry, but your code snipped does not represent Doubly-Checked
Locking Pattern. The whole idea of DCLP is to forgo the locking in case
when "shared_field" has been initialized. Your code will take the lock
in any event.

Furthermore, if your implementation of LockGuard involves POSIX calls,
then there's no need for any MEMORY_BARRIER.

Andy.


--

Miles Bader

unread,
Oct 27, 2010, 2:45:12 AM10/27/10
to
Andy Venikov <swojch...@gmail.com> writes:
> I'm sorry, but your code snipped does not represent Doubly-Checked
> Locking Pattern. The whole idea of DCLP is to forgo the locking in
> case when "shared_field" has been initialized. Your code will take
> the lock in any event.

You misunderstand: I was giving only the part of the code that gets
executed in the "slow" case, when shared_field == NULL. I did note
this in my previous message, though perhaps I didn't emphasize it
enough.


I.e., the real code looks like:


class Foo
{
public:
...

T *get_shared_field ()
{
if (! shared_field)
update_shared_field ();
return shared_field;
}
private:

void update_shared_field ();

T *shared_field;
};
...
void
Foo::update_shared_field ()
{
... code from my previous message ...
}


Thanks,

-Miles

--
If you can't beat them, arrange to have them beaten. [George Carlin]

Andy Venikov

unread,
Oct 27, 2010, 8:26:04 PM10/27/10
to
On 10/27/2010 2:45 AM, Miles Bader wrote:
<snip>

> You misunderstand: I was giving only the part of the code that gets
> executed in the "slow" case, when shared_field == NULL. I did note
> this in my previous message, though perhaps I didn't emphasize it
> enough.
>
>
> I.e., the real code looks like:
>
>
> class Foo
> {
> public:
> ...
>
> T *get_shared_field ()
> {
> if (! shared_field)
> update_shared_field ();
> return shared_field;
> }
> private:
>
> void update_shared_field ();
>
> T *shared_field;
> };
> ...
> void
> Foo::update_shared_field ()
> {
> ... code from my previous message ...
> }
>
>
> Thanks,
>
> -Miles
>

Oh, I see, it wasn't the complete code.

But in this case you would need another memory barrier before reading
shared_field. Remember, memory barriers must be used on both ends.
So, your get_shared_field should look look like this:

READ_BARRIER();

if (!shared_field)
update_shared_field();
return shared_field.

Without the read barrier here it's still possible for things to go awack
even with the presence of a barrier at the write site.

Andy.


--

Chris M. Thomasson

unread,
Oct 27, 2010, 8:23:26 PM10/27/10
to
"Miles Bader" <mi...@gnu.org> wrote in message news:87iq0op...@catnip.gol.com...

> Andy Venikov <swojch...@gmail.com> writes:
>> I'm sorry, but your code snipped does not represent Doubly-Checked
>> Locking Pattern. The whole idea of DCLP is to forgo the locking in
>> case when "shared_field" has been initialized. Your code will take
>> the lock in any event.
>
> You misunderstand: I was giving only the part of the code that gets
> executed in the "slow" case, when shared_field == NULL. I did note
> this in my previous message, though perhaps I didn't emphasize it
> enough.
>
>
> I.e., the real code looks like:
[...]

Here is a simple sketch of DCL:

<pseudo-code>
__________________________________________________________________
template<typename T>
static T& once()
{
static atomic<T*> g_global(NULL);

// data-dependant load acquire barrier to sync with release...
T* local = g_global.load(memory_order_consume);

if (! local)
{
static pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;

pthread_mutex_lock(&g_mutex);

if (! (local = g_global.load(memory_order_relaxed)))
{
try
{
local = new T();
}

catch
{
pthread_mutex_unlock(&g_mutex);
throw;
}

// store release barrier to sync with dependant acquire...
g_global.store(local, memory_order_release);
}

pthread_mutex_unlock(&g_mutex);
}

return *local;
}
__________________________________________________________________


--

Chris M. Thomasson

unread,
Oct 28, 2010, 11:16:16 AM10/28/10
to
"Andy Venikov" <swojch...@gmail.com> wrote in message
news:ia9mj9$i45$1...@news.eternal-september.org...

> On 10/27/2010 2:45 AM, Miles Bader wrote:
> <snip>
>
>> You misunderstand: I was giving only the part of the code that gets
>> executed in the "slow" case, when shared_field == NULL. I did note
>> this in my previous message, though perhaps I didn't emphasize it
>> enough.
>>
>>
>> I.e., the real code looks like:
[...]

> Oh, I see, it wasn't the complete code.
>
> But in this case you would need another memory barrier before reading
> shared_field. Remember, memory barriers must be used on both ends.
> So, your get_shared_field should look look like this:
>
> READ_BARRIER();
>
> if (!shared_field)
> update_shared_field();
> return shared_field.
>
> Without the read barrier here it's still possible for things to go awack
> even with the presence of a barrier at the write site.

Actually, you need to place the membar _after_ the load Andy.

Andy Venikov

unread,
Oct 29, 2010, 10:12:05 AM10/29/10
to
On 10/28/2010 11:16 AM, Chris M. Thomasson wrote:
<snip>

>> So, your get_shared_field should look look like this:
>>
>> READ_BARRIER();
>>
>> if (!shared_field)
>> update_shared_field();
>> return shared_field.
>>
>> Without the read barrier here it's still possible for things to go awack
>> even with the presence of a barrier at the write site.
>
> Actually, you need to place the membar _after_ the load Andy.
>
>

Yes, of course you're right. I should've taken an extra second to think about it.
It just goes to show that every time you need to deal with barriers, you have to
be extra careful.

In this particular case, you want to prevent the re-ordering of reads of
1)shared_field and 2) the object that it points to. So, of course you need to
separate these two reads with a read barrier.

Andy Venikov

unread,
Oct 30, 2010, 12:46:46 PM10/30/10
to
On 10/29/2010 10:12 AM, Andy Venikov wrote:
> On 10/28/2010 11:16 AM, Chris M. Thomasson wrote:
> <snip>
>
>>> So, your get_shared_field should look look like this:
>>>
>>> READ_BARRIER();
>>>
>>> if (!shared_field)
>>> update_shared_field();
>>> return shared_field.
>>>
>>> Without the read barrier here it's still possible for things to go awack
>>> even with the presence of a barrier at the write site.
>>
>> Actually, you need to place the membar _after_ the load Andy.
>>
>>
>
> Yes, of course you're right. I should've taken an extra second to think
> about it.
> It just goes to show that every time you need to deal with barriers, you
> have to be extra careful.
>
> In this particular case, you want to prevent the re-ordering of reads of
> 1)shared_field and 2) the object that it points to. So, of course you
> need to separate these two reads with a read barrier.
>
> Andy.
>

Ok, there's actually more to it than that.

The reason you would want a read barrier after the first read of "shared_field"
is not to prevent re-ordering of this read from the read of the object
"shared_field" points to. For that a data dependency barrier, which on most all
hardware platforms happens automatically, would be sufficient. But to prevent
the re-ordering of the two reads of "shared_field".

But as it turns out, in this case the explicit call to read_barrier is not
necessary.

The lock.acquire() and lock.release() calls establish a critical section. It is
guaranteed that the code inside of the critical section won't move outside. It
is however quite possible for the code outside of the critical section to move
in. So, the only way it is possible for the two loads of "shared_field" to get
out-of-order is if the first load migrates inside of the critical section and
happens after the second load. But in that case, since both happen inside of the
critical section, they will produce the same result. It's as if lock.acquire()
produces a read barrier in case when first read happens outside of the critical
section. But we still need the write_barrier() to separate the write of the
object (object construction) from the write of "shared_field".

We've come full circle to the original implementation.
But I bet the real reason why we don't need a read barrier here wasn't clear at
the beginning and the code "just worked".

Thanks,

0 new messages