Re: strange threading context
"A.Gallus" <uh5d@rz.uni-karlsruhe.de> wrote in message
news:g87gvg$u73$1@news2.rz.uni-karlsruhe.de...
This is my problem:
I have two classes A and B:
class A
{
void methodA();
}
class B
{
void methodB();
struct S
{
int Size;
};
S & getMyStruct();
S MyStruct;
}
methodA and methodB get executed in two different threads concurrently.
They both access the variable Size in the structure MyStruct of type S.
methodA gets a reference to this struct by calling getMyStruct which
returns a reference
to it.
Here comes the strange part:
if methodB alters the value of Size from 0 to 1, this change doesn't get
reflected in methodA - methodA
still reads a 0. The access to the struct MyStruct is sychronized by a
mutex.
Well, let me try to read your mind and see if I can make a better model in
the form of fully compliable code:
code listing 1 with a race-condition!
______________________________________________________________________
#include <cstdio>
#include <pthread.h>
class mutex_guard {
pthread_mutex_t* const m_mutex;
public:
mutex_guard(pthread_mutex_t* const mutex)
: m_mutex(mutex) {
pthread_mutex_lock(m_mutex);
}
~mutex_guard() {
pthread_mutex_unlock(m_mutex);
}
};
class A {
public:
void methodA();
};
class B {
friend class A;
struct S {
int Size;
S() : Size() {}
};
S MyStruct;
S& getMyStruct() {
return MyStruct;
}
static pthread_mutex_t g_mutex;
public:
void methodB();
};
pthread_mutex_t B::g_mutex = PTHREAD_MUTEX_INITIALIZER;
static B g_global_instance_of_B;
static A g_global_instance_of_A;
void A::methodA() {
mutex_guard lock(&B::g_mutex);
std::printf("A::methodA() - B::MyStruct::Size == %d\n",
g_global_instance_of_B.getMyStruct().Size);
}
void B::methodB() {
mutex_guard lock(&B::g_mutex);
MyStruct.Size = 1;
std::printf("B::methodB() - B::MyStruct::Size == %d\n",
g_global_instance_of_B.getMyStruct().Size);
}
extern "C" void* thread_1(void*) {
g_global_instance_of_A.methodA();
return NULL;
}
extern "C" void* thread_2(void*) {
g_global_instance_of_B.methodB();
return NULL;
}
int main() {
pthread_t tid[2];
pthread_create(&tid[0], NULL, thread_1, NULL);
pthread_create(&tid[1], NULL, thread_2, NULL);
for (int i = 0; i < 2; ++i) {
pthread_join(tid[i], NULL);
}
return 0;
}
______________________________________________________________________
Okay, this program can run 3 ways... The first is that thread_1 runs first
and therefore misses the update. Second, thread_2 runs first and the update
is observed when thread_1 runs. The third is that they run together and race
through in which anything can happen. For instance, if thread_1 hits the
mutex first, then the update will not be observed.
If you want to ensure that thread_1 will ALWAYS see the mutation generated
by thread_2, well, you need someway to make sure that thread_2 runs first. A
simple and niave soultion is to use a simple semaphore. Think of this:
code listing 2 without a race-condition!
______________________________________________________________________
#include <cstdio>
#include <pthread.h>
#include <semaphore.h>
class mutex_guard {
pthread_mutex_t* const m_mutex;
public:
mutex_guard(pthread_mutex_t* const mutex)
: m_mutex(mutex) {
pthread_mutex_lock(m_mutex);
}
~mutex_guard() {
pthread_mutex_unlock(m_mutex);
}
};
class A {
public:
void methodA();
};
class B {
friend class A;
struct S {
int Size;
S() : Size() {}
};
S MyStruct;
S& getMyStruct() {
return MyStruct;
}
static pthread_mutex_t g_mutex;
public:
void methodB();
};
pthread_mutex_t B::g_mutex = PTHREAD_MUTEX_INITIALIZER;
static B g_global_instance_of_B;
static A g_global_instance_of_A;
void A::methodA() {
mutex_guard lock(&B::g_mutex);
std::printf("A::methodA() - B::MyStruct::Size == %d\n",
g_global_instance_of_B.getMyStruct().Size);
}
void B::methodB() {
mutex_guard lock(&B::g_mutex);
MyStruct.Size = 1;
std::printf("B::methodB() - B::MyStruct::Size == %d\n",
g_global_instance_of_B.getMyStruct().Size);
}
extern "C" void* thread_1(void* state) {
sem_t* const sem = (sem_t*)state;
sem_wait(sem);
g_global_instance_of_A.methodA();
return NULL;
}
extern "C" void* thread_2(void* state) {
sem_t* const sem = (sem_t*)state;
g_global_instance_of_B.methodB();
sem_post(sem);
return NULL;
}
int main() {
pthread_t tid[2];
sem_t sem;
sem_init(&sem, 0, 0);
pthread_create(&tid[0], NULL, thread_1, &sem);
pthread_create(&tid[1], NULL, thread_2, &sem);
for (int i = 0; i < 2; ++i) {
pthread_join(tid[i], NULL);
}
sem_destroy(&sem);
return 0;
}
______________________________________________________________________
Now, thread_1 will always see the mutation made by thread_2. Period, end of
story. thread_1 will ALWAYS output:
A::methodA() - B::MyStruct::Size == 1
Whats going on here? Why are the changes of Size by thread executing
methodB not visible to the thread executing methodA.
I thought that struct myStruct lies in the context of both
threads/methods.
Perhaps the thread in which methodA gets invoked happens to execute first,
thus it has no chance to observe the mutation made by the thread which
invokes methodB.