开发者

Is this rule about volatile usage strict?

I've seen this sentence:

the general rule is, if you have variables of primitive type that must be shared among multiple threads, declare those variables volatile

from this article, and this sentence:

In g开发者_运维知识库eneral, any data that may be undated asynchronously should be declared to be volatile.

from this page, now considering this introduced rule I'd like to know could you bring an example of a case where despite existence of asynchronous access to a data declaring that data volatile has no use in practice or there's no such exceptional case and the rule is strict.


I remember when that article was published and I remember the endless discussions that then followed on comp.lang.c++.moderated.

IIRC, Andrei hijacks the volatile keyword to use it to discriminate between different function overloads. (See this article by Scott Meyers for another such an idea.) What he does is brilliant, in that it allows the compiler to catch you if you mess up protected and unprotected access to objects (very much like the compiler catches you should you try to modify a constant). But besides the fact that it helps you, it has nothing to do with actually protecting concurrent access to objects.

The problem is only that 90% of the people have one glance at the article and all they see is volatile and "threads" in the same article. Depending on their knowledge, they then either draw the wrong conclusion that volatile is good for threads (you seem to have done so) or they yell at him for leading others to draw the wrong conclusions.
Very few people seem to be able to actually read the article thoroughly and understand what he really does.


I can't speak for the actual asynchronous access scenario, since I'm not too good at multithreading, but what the volatile modifier does is tell the compiler:

"Listen, this may change at any time, so don't cache it or put it in a register or do anything crazy like that, okay?"

It does not protect against asynchronous writes, it simply disables optimizations that are invalid if the variable can be changed by external forces.

Edit: As a potential example, one that doesn't involve multithreading (but, does involve exceptionally convoluted code ;), here's a case where volatile is important:

volatile bool keepRunning = true;
void Stuff() {
    int notAPointer = 0;

    notAPointer = (int)(&keepRunning); //Don't do this! Especially on 64-bit processors!

    while(keepRunning) {
        *(bool*)(notAPointer) = false;
    }

    printf("The loop terminated!");
}

Without that volatile modifier, the compiler might go "Hey, keepRunning is never modified, so I don't even need to generate code that checks it!", when in reality we're simply modifying it in secret.

(In reality, this would probably still work on a non-optimized build. And, it might also still work if the compiler is smart and notices the pointer being taken. But, the principle is the same)


Read this. Volatile has nothing to do with multi-threading.


To follow up on Mike's answer, it's useful in cases like this (global variables used to avoid complexity for this example):

static volatile bool thread_running = true;

static void thread_body() {
    while (thread_running) {
        // ...
    }
}

static void abort_thread() {
    thread_running = false;
}

Depending on how complex thread_body is, the compiler may elect to cache the value of thread_running in a register when the thread begins running, which means it will never notice if the value changes to false. volatile forces the compiler to emit code that will check the actual thread_running variable on every loop.


I would propose a considerably more strict but very useful rule: if you do not understand exactly what volatile does, do not use it. Instead, use lock. If you do not understand exactly what lock does and how to use it, do not use multithreading.


I would say that in theory those rules are absolutely right, and volatile is needed every time when a variable is accessed by 2 threads. (Even when using mutexes, cause they don't prevent compiler optimizations.) But in practice compilers are good enough at recognizing situations where a variable might be modified outside a particular function so that its value shouldn't be cached in registers. In most cases volatile is not necessary.

I once did some testing in MSVC by inspecting the assembler output for different situations, and all it took to prevent a variable from being cached was to have another function writing to the same variable or taking its address. Global variables were never optimized unless "whole program optimization" was turned on (so the compiler can be sure the variable is not modified in other translation units).


Likewise, the C++ standard does not specify how volatile should work, you have to look at what a particular compiler does for a particular platform. Also volatile is a bit subtle and what it does, depends on the compiler and the memory model of the target platform. Locks are way more intuitive to use.


Before you take the article you linked to first too seriously, you might want to read another. In a later posting on Usenet, Andrei later admitted that parts of the original article were just plain wrong, and pointed to this one as giving a more realistic view of things (though note that the link he gives to it there seems to have gone stale).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜