When writing C programs that need to share a file scope variable between the application and an interrupt routine/a thread/a callback routine, it is well-known that the variable must be declared volatile, or else the compiler may do incorrect optimizations. This is an example of what I mean:
int flag;
void some_interrupt (void)
{
flag = 1;
}
int main()
{
flag = 0;
...
/* <-- interrupt occurs here */
x = flag; /* BUG: the compiler doesn't realize that "flag" was changed
and sets x to 0 even though flag==1 */
}
To prevent the above bug, "flag" should have been declared as volatile.
My question is: how does this apply to C++ when creating a class containing a thread?
I have a class looking something like this:
class My_thread
{
private:
int flag;
static void thread_func (void* some_arg) // thread callback function
{
My_thread* this_ptr= (My_thread*)some_arg;
}
};
"some_arg" will contain a pointer to an instance of the class, so that each object of "My_thread" has its own thread. Through this pointer it will access member variables.
Does this mean that "this_ptr" must be declared as pointer-to-volatile data? Must "flag" be volatile as well? And if so, must I make all member functions that modify "flag" volatile?
I'm not interested in how a particular OS or compiler behaves, I am looking for a generic, completely portable solution.
EDIT: This question has nothing to do with thread-safety whatsoever!
The real code will have semaphores etc.
To clarify, I wish to avoid bugs caused by the compiler's unawareness of that a callback function may be called from sources outside the program itself, and therefore make incorrect conclusions about whether certain variables have been used or not. I know how to do this in C, as illustrated with the first example, bu开发者_开发百科t not in C++.
Well, that edit makes all the difference of the world. Semaphores introduce memory barriers. Those make volatile
redundant. The compiler will always reload int flag
after any operation on a semaphore.
Fred Larson already predicted this. volatile
is insufficient in the absence of locks, and redudant in the presence of locks. That makes it useless for thread-safe programming.
From the function pointer signature I guess you are using the posix thread implementation for threads. I assume you want to know how to start off a thread using this API. First consider using boost thread instead. If not an option, I usually go for something like the following to get somewhat of that cosy Java readability.
class Runnable {
public:
virtual void run() = 0;
};
class Thread : public Runnable {
public:
Thread();
Thread(Runnable *r);
void start();
void join();
pthread_t getPthread() const;
private:
static void *start_routine(void *object);
Runnable *runner;
pthread_t thread;
};
And then something like this in the start_routine
function:
void* Thread::start_routine(void *object) {
Runnable *o = (Runnable *)object;
o->run();
pthread_exit(NULL);
return NULL;
}
Now access to fields of classes extending the Runnable or Thread class need not be volatile since they are thread-local.
That said, sharing data between threads is more complex than using a volatile data member unfortunately if that is what you asked...
Read this article by Andrei Alexandrescu over at Dr. Dobbs, it might be relevant:
volatile - Multithreaded Programmer's Best Friend
From the intro to the article:
The volatile keyword was devised to prevent compiler optimizations that might render code incorrect in the presence of certain asynchronous events. For example, if you declare a primitive variable as volatile, the compiler is not permitted to cache it in a register -- a common optimization that would be disastrous if that variable were shared among multiple threads. So the general rule is, if you have variables of primitive type that must be shared among multiple threads, declare those variables volatile. But you can actually do a lot more with this keyword: you can use it to catch code that is not thread safe, and you can do so at compile time. This article shows how it is done; the solution involves a simple smart pointer that also makes it easy to serialize critical sections of code.
Some implementation of the fallback mechanism is given here for both Windows and Linux. Try this example:
typeReadFileCallback varCallback;
I was able to implement using that.
精彩评论