C++ Multithreading, Volatile, and Correctness

Hi, I am working on a project with boost::threads, and I need to be absolutely sure that the method calls between mutex::lock() and mutex::unlock() are not reordered when compiled with different optimizations and compilers. I believe the volatile keyword may be of some use, but honestly, I am unsure whether or not I am using it correctly. Here is some example code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
class obj : boost::mutex {
 std::vector<int> data;
 void foo(int x) {
  lock();
  data.push_back(x);
  unlock();

  x = x*x;

  lock();
  data.push_back(x);
  unlock();
 }
 void bar(int x) volatile {
  lock();
  data.push_back(x);
  unlock();

  x = x*x;

  lock();
  data.push_back(x);
  unlock();
 }
};
int main() {
 obj o;
 o.foo(2); // not volatile
 o.bar(2); // is volatile
}


So, my question is: how can I be sure the lock and unlock functions are not reordered? Since there are no data dependencies between the mutex lock and the data, the compiler may see two function calls on a related object followed by two functions calls on another related object. Then it may decide to reorder the function calls to put lock() unlock() together followed by data.push_back(x), data.push_back(x).

I have a feeling the volatile function bar may maintain the order, but I am also not sure what other undesirable side effects the volatile keyword may have.

Thanks,
Jeff Kunkel
Last edited on
I don't think optimizations arbitrarily reorder your code...that would cause a lot of problems. Other than that, this locking isn't exception safe either.

For make it so, use RAII:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
struct LocalLock {
    LocalLock() { lock(); }
    ~LocalLock() { unlock(); }
};
//...
void f() {
    { //lock
        LocalLock m;
        data.push_back(x);
    } // unlock
    //...
    { //lock
        LocalLock m;
        data.push_back(x);
    } // unlock
}
Last edited on
moorecm wrote:
I don't think optimizations arbitrarily reorder your code


+1 @ this. They won't.

C++ guarantees that sequence points are run in order (hence the name "sequence points"). Here, sequence points are marked by the semicolon:

1
2
3
4
5
int main() {
 obj o;
 o.foo(2); // semicolon here means foo() will finish before
 o.bar(2);   //  bar() even starts
}



Compilers can remove code that they think is useless, though. But I doubt that's happening here.


Also +1 to moorecm's RAII approach.
Topic archived. No new replies allowed.