1

The question is simple. Does/Should a variable used with multi-threads be volatile even accessed in critical section(i.e. mutex, semaphore) in C? Why / Why not?

#include <pthread.h>

  volatile int account_balance;
  pthread_mutex_t flag  = PTHREAD_MUTEX_INITIALIZER;

  void debit(int amount) {
   pthread_mutex_lock(&flag);
   account_balance -= amount;//Inside critical section
   pthread_mutex_unlock(&flag);
 }

What about the example or equivalently thinking for semaphore?

concurrencyboy
  • 351
  • 1
  • 11
  • https://stackoverflow.com/a/78221/635608 – Mat Aug 13 '17 at 17:34
  • Why / Why not? @Mat – concurrencyboy Aug 13 '17 at 17:36
  • You want me to re-type the Intel article linked there here in comments? or retype the other answers? – Mat Aug 13 '17 at 17:37
  • In short: Most time you don't want volatile, even in multithreaded applications. Volatile does something different than what most people expect. – Al Kepp Aug 13 '17 at 17:39
  • I see a comment on which it is written "the article is wrong ....." on Intel's @Mat – concurrencyboy Aug 13 '17 at 17:39
  • 1
    In the example you've given, the `volatile` keyword will have no effect on the observable output of the program. Therefore, you should not be using it. – user3386109 Aug 13 '17 at 17:42
  • Why / Why not? @user3386109 – concurrencyboy Aug 13 '17 at 17:43
  • You seem to be asking for a tutorial on two broad topics: concurrency and the `volatile` keyword. That's *why* your question is too broad for SO. – user3386109 Aug 13 '17 at 17:45
  • I'm not an experienced or professional programmer just for now. So how can I comprehend it about in the view of concurrency? @user3386109 – concurrencyboy Aug 13 '17 at 17:48
  • You need to find books, or tutors, or classes that will teach you about concurrency. – user3386109 Aug 13 '17 at 17:49
  • @concurrencyboy *I see a comment on which it is written "the article is wrong ..." on Intel's* Sooo, someone at Intel - of all places - writes an article on the correct use of `volatile` - citing *Hans Boehm* off all people - and you want to believe some anonymous commenters? The comments saying the article is wrong are at best idiotic. The last comment says it best: **If you are simply adding 'volatile' to variables that are shared between threads thinking that fixes your shared-data problem without bothering to understand why it may not, you will eventually reap the reward you deserve.** – Andrew Henle Aug 13 '17 at 17:53
  • 1
    Volatile here is used to **force** read and the next store when changed from/to its memory storage as the register storage may be invalid at this point. otherwise if there is no visible path to anything changing it it may be optimised, and potentially removed from the generated code. so the answer depends on the rest of program and those couple of lines do not provide any clues if it is needed or not. Anyway - if programmer considers important vatiable to have memory storage, and to be read and written - should use volatile – 0___________ Aug 13 '17 at 18:04
  • @PeterJ But that's not the same thing as multithreaded access to the variable. While it's possible to use `volatile` to do things like lock-free multithreaded programming, such code will be *extremely* platform particular, and should only be written by someone who fully understands the platform in use, the compiler being used, and the exact implications of `volatile` on that platform and compiler combination. I run far, far away from trying to do things like that. – Andrew Henle Aug 13 '17 at 18:09
  • @Andrew Henle `But that's not the same thing as multithreaded access to the variable.` I did not say that. I just said if there is no path to the thread visible at compile time - compiler may do optimisations not expected by the coder. For example in the function increasing the static variable not accessed anywhere else in the program execution path (starting the thread is not the execution path from the compiler point of view - same as enabling the interrupt) - compiler will probably optimise those operations out. **I did not say that `volatile` makes variable thread safe** – 0___________ Aug 13 '17 at 18:18
  • volatile guarantees also that the variable variable will have permanent memory storage, will be read from its permanent storage as many times as is used and written as many times as it is changed, but with no guarantee of atomicity, coherency etc etc – 0___________ Aug 13 '17 at 18:20
  • @PeterJ ***I did not say that volatile makes variable thread safe*** I understand that you know that. Given some of the comments, though, there seems to be a belief that `volatile` *does* make a variable thread-safe. My comment was meant more for that audience. My apologies for any misunderstanding. – Andrew Henle Aug 13 '17 at 18:40
  • @Andrew Henle no worries :). I know what you mean. I was doing interviews some time ago (I work for the bare metal embedded industry) - 80% of candidates was sure that `volatile` means `atomic`. (and the requirement was : `very good knowledge of C` I know it is a very common misunderstanding. – 0___________ Aug 13 '17 at 18:47
  • @PeterJ It does no such thing. And, in fact, on almost no modern platform that you are likely to use does it have that effect. And you would not need that anyway, since common platforms have hardware cache coherence. – David Schwartz Aug 14 '17 at 00:45
  • @DavidSchwartz volatile has nothing to do with the cache, or cache coherency, and it does exactly what he said. it prevents the compiler from storing the variable in a register for quick access, and it's highly likely that you don't want that when sharing a variable across multiple threads, and you definitively don't want that across your critical section boundry – dreta Jun 06 '18 at 12:38
  • @PeterJ_01 you're absolutely correct, and you need volatile to ensure that your critical sections ACTUALLY WORK. it's so frustrating that seemingly everybody on the internet confuse volatile with atomicity or thread safety. it's like you talk about apples, but people are trying to convince you with all their lack of real-world knowledge that oranges – dreta Jun 06 '18 at 12:42
  • @dreta On some platforms, it might happen to prevent the compiler from storing the variable in a register for quick access. But that's neither necessary (because every sensible platform provides better ways) nor sufficient (because the CPU can optimize out accesses too) to help you share a variable across threads. – David Schwartz Jun 10 '18 at 21:57
  • @DavidSchwartz No, it does prevent the compiler from optimizing accesses away, on all platforms. You should use volatile when that's a requirement, f.e. when dealing with accesses within critical sections, like in the question. CPU's don't optimize accesses away, they can use a cache, but that's synchronized across cores, and hardware registers aren't cached. Accesses can be shfited around, which is why you use barriers, but that's not a part of the C standard. Stop with the hand-wavy vagueness, you're spreading lies. – dreta Jun 11 '18 at 20:22
  • @dreta You're reasoning is completely and totally nonsensical. We're talking about what standards require, not what typical CPUs happen to do. That CPUs you know about don't optimize accesses away tells us nothing about what future CPUs or CPUs you don't know about will do. That caches are synchronized is a property of CPUs that you happen to care about. You are giving people harmful bogus information that leads to people writing code that breaks when new CPUs or compilers come out. – David Schwartz Jun 13 '18 at 20:07
  • We had an entire generation of programmers that wrote code that broke on modern multicore CPUs because they assumed that newer CPUs wouldn't do things they didn't see their current CPUs do. Please don't misinform people into repeating this cycle. We have standards that provide guarantees for a reason. If your platform doesn't provide sufficient guarantees in its standards and you have to rely on the kind of reasoning you're using above, it's a crappy platform. And no modern, popular platforms are crappy in that way. – David Schwartz Jun 13 '18 at 20:09
  • We’re talking about a C compiler, not what ever the CPU does. That’s a different story. If you’re writing multithreaded code in C, you should make your variables volatile, it’s that simple, and no one claims this will make your code magically work. You’re making up utter garbage, and straw man arguments to back up your ridiculous claims. Learn C before commenting on a question regarding the language. – dreta Jun 15 '18 at 16:47

3 Answers3

3

Does/Should a variable used with multi-threads be volatile even accessed in critical section(i.e. mutex, semaphore) in C? Why / Why not?

No.

volatile is logically irrelevant for concurency, because it's not sufficient.

Actually, that's not really true - volatile is not irrelevant because it can hide concurrency problems in your code, so it works "most of the time".

All volatile does is tell the compiler "this variable can change outside the current thread of execution". Volatile in no way enforces any ordering, atomicity, or - critically - visibility. Just because thread 2 on CPU A changes int x, that doesn't mean thread 1 on CPU D can even see the change at any specific time - it has it's own cached value, and volatile means almost nothing with respect to memory coherence because it doesn't guarantee ordering.

The last comment at the bottom of the Intel article Volatile: Almost Useless for Multi-Threaded Programming says it best:

If you are simply adding 'volatile' to variables that are shared between threads thinking that fixes your shared-data problem without bothering to understand why it may not, you will eventually reap the reward you deserve.

Yes, lock-free code can make use of volatile. Such code is written by people who can likely write tutorials on the use of volatile, multithreaded code, and other extremely detailed subjects regarding compilers.

Andrew Henle
  • 32,625
  • 3
  • 24
  • 56
1

No, volatile should not be used on shared variables which are accessed under the protection of pthreads synchronisation functions like pthread_mutex_lock().

The reason is that the synchronisation functions themselves are guaranteed by POSIX to provide all the necessary compiler barriers and synchronisation to ensure consistency (as long as you follow the POSIX rules on concurrent access - ie. that you have used pthreads synchronisation functions to ensure that no thread can be writing to a shared variable whilst another thread is writing to or reading from it).

caf
  • 233,326
  • 40
  • 323
  • 462
0

I have no idea why there's so much misinformation about volatile everywhere on the internet. The answer to your question is yes, you should make variables you use within a critical section volatile.

I'll give a contrived example. Let's say you want to run this function on multiple threads:

int a;

void inc_a(void) {
    for (int i = 0; i < 5; ++i) {
        a += 5;
    }
}

Everybody, as it would seem, on this site will tell you that it's enough to put a += 5 in a critical section like so:

int a;

void inc_a(void) {
    for (int i = 0; i < 5; ++i) {
        enter_critical_section();
        a += 5;
        exit_critical_section();
    }
}

As i said, it's contrived, but people will tell you this is correct, and it absolutely is not! If the compiler wasn't given prior knowledge as to what the critical section functions are, and what their semantic meaning is, there's nothing stopping the compiler from outputting this code:

int a;

void inc_a(void) {
    register eax = a;

    for (int i = 0; i < 5; ++i) {
        enter_critical_section();
        eax += 5;
        exit_critical_section();
    }

    a = eax;
}

This code produces the same output in a single threaded context, so the compiler is allowed to do that. But in a multithreaded context, this can output anything between 25 and 25 times the thread count. One way to solve this issue is to use an atomic construct, but that has performance implications, instead what you should do is make the variable volatile. That is, unless you want to be like the rest of this community and blindly put your faith in your C compiler.

dreta
  • 1,016
  • 3
  • 13
  • 22