In java, you’ll code something like:
class MySharedObject
{
public int myData;
public void mySharedFunc(int o) synchronized
{
myData += o;
}
MySharedObject() { myData = 0; }
};
// In one thread, you'll do :
MySharedObject obj = getMySharedObj();
obj.mySharedFunc(3);
// In the other thread, you'll do :
MySharedObject obj = getMySharedObj();
obj.mySharedFunc(2);
In Java, because of the “synchronized” keyword, you are sure that myData will be 5 after the 2 threads, because entering the mySharedFunc is done internally by locking MySharedObject’s mutex.
If you write this code in C++:
class MySharedObject
{
public: int myData;
void mySharedFunc(int o)
{
myData += o;
}
MySharedObject() { myData = 0; }
};
// In one thread, you'll do :
MySharedObject &obj = getMySharedObj();
obj.mySharedFunc(3);
// In the other thread, you'll do :
MySharedObject &obj = getMySharedObj();
obj.mySharedFunc(2);
You have no guarantee of the result of myData. If the first thread is stopped just after “load myData value in a register, add o value to the register”, then the 2nd thread execute entirely, the first thread finish “store the register back in myData” will result in myData being 3 not 5.
The gross solution is to lock a critical section while entering mySharedFunc(), and release it while leaving (so that the 2nd thread can’t execute until the first thread has finished)
However, if later on, you had other shared methods and forgot to lock the section, you’ll hardly understand the errors you’ll get.
The article above give a better solution which is:
class MySharedObject
{
public: int myData;
void mySharedFunc(int o)
{
myData += o;
}
MySharedObject() { myData = 0; }
// Multithreading access
volatile Lock lock;
void mySharedFunc(int o) volatile
{
lock.Acquire();
const_cast<MySharedObject*>(this)->mySharedFunc(o); // Call the non-volatile version
lock.Release();
}
};
// In one thread, you'll do :
volatile MySharedObject &obj = getMySharedObj();
obj.mySharedFunc(3); // Will call the volatile version
// In the other thread, you'll do :
volatile MySharedObject &obj = getMySharedObj();
obj.mySharedFunc(2); // Will call the volatile version
This improvement guarantee that if, later on, you add a method without making a volatile version of it, it won’t compile ( a volatile object calls its volatile methods)
This means that the multithreading code can be checked at compile-time for safety, not at jeopardize your run-time behavior.