views:

297

answers:

2

When I try to create a class instance with a virtual method and pass it to pthread_create, I get a race condition, causing the caller to sometimes call the base method instead of the derived method like it should. After googling pthread vtable race, I found out this is fairly well-known behavior. My question is, what's a good way to get around it?

The code below exhibits this behavior at any optimization setting. Note that the MyThread object is completely constructed before being passed to pthread_create.

#include <errno.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Thread {
    pthread_t thread;

    void start() {
     int s = pthread_create(&thread, NULL, callback, this);
     if (s) {
      fprintf(stderr, "pthread_create: %s\n", strerror(errno));
      exit(EXIT_FAILURE);
     }
    }
    static void *callback(void *ctx) {
     Thread *thread = static_cast<Thread*> (ctx);
     thread->routine();
     return NULL;
    }
    ~Thread() {
     pthread_join(thread, NULL);
    }

    virtual void routine() {
     puts("Base");
    }
};

struct MyThread : public Thread {
    virtual void routine() {

    }
};

int main() {
    const int count = 20;
    int loop = 1000;

    while (loop--) {
     MyThread *thread[count];
     int i;
     for (i=0; i<count; i++) {
      thread[i] = new MyThread;
      thread[i]->start();
     }
     for (i=0; i<count; i++)
      delete thread[i];
    }

    return 0;
}
+3  A: 

The only problem here is that you are deleting the objects before the spawned thread executes the method, so by that time the child destructor already fired and the object is not there anymore.

So it has nothing to do with pthread_create or whatever, its your timing, you can't spawn a thread, give it some resources and delete them before he has the chance of using them.

Try this, it'll show how the objs are destructed by main thread before spawned thread uses them:

struct Thread {
pthread_t thread;
bool deleted;

void start() {
    deleted=false;
    int s = pthread_create(&thread, NULL, callback, this);
    if (s) {
            fprintf(stderr, "pthread_create: %s\n", strerror(errno));
            exit(EXIT_FAILURE);
    }
}
static void *callback(void *ctx) {
    Thread *thread = static_cast<Thread*> (ctx);
    thread->routine();
    return NULL;
}
~Thread() {
    pthread_join(thread, NULL);
}

virtual void routine() {
    if(deleted){
        puts("My child deleted me");
    }
    puts("Base");
}
};

struct MyThread : public Thread {
virtual void routine() {

}
~MyThread(){
    deleted=true;
}

};

In the other hand if you just place a sleep in main before deleting them you'll never have that problem because the spawned thread is using valid resources.

int main() {
const int count = 20;
int loop = 1000;

while (loop--) {
    MyThread *thread[count];
    int i;
    for (i=0; i<count; i++) {
            thread[i] = new MyThread;
            thread[i]->start();
    }
    sleep(1);
    for (i=0; i<count; i++)
            delete thread[i];
}

return 0;
}
Arkaitz Jimenez
Thanks, that was it! Nice catch, and great answer!
Joey Adams
Yes. It makes it harder to hit race condition because thanks to sleep(1) thread in example will finish before delete thread[i]. But not impossible. You still can get situation when the thread routine() doesn't finish (or even start) before destructor is called. So it will try to use partially destroyed object which will trigger the problem. The proper solution is to destroy object only after the call to pthread_join confirms thread has finished.
Tomek Szpakowicz
Its a matter of either giving the spawned thread enough time or ensure it has already done its job before deleting resources, its just resources dependency.
Arkaitz Jimenez
How much time is enough?
Tomek Szpakowicz
Tomek's right. Adding a `sleep` is never a robust fix for a race condition. There is no guarantee that those other threads will run *at all* during the sleep: maybe the machine is running some other program which just so happens to eat up that entire second. If you want to check that a thread has completed, then that's what join does. Nothing else does it.
Steve Jessop
+2  A: 

Do not do pthread_join (or any other real work) in destructor. Add join() method to Thread and call it before delete thread[i] in main.

If you try to call pthread_join in destructor, thread may be still executing Thread::routine(). Which means it's using object that is already partially destroyed. What will happen? Who knows? Hopefully program will crash quickly.


Additionally:

  • If you wish to inherit from Thread, Thread::~Thread should be declared virtual.

  • Check for all errors and handle them properly (which BTW cannot be done inside destructor).

Tomek Szpakowicz
Sorry. I mixed up both versions while writing answer. I deleted parts that don't apply.
Tomek Szpakowicz
+1 for taking join out of the base class destructor.
Steve Jessop
Assuming it is done correctly, `join` is precisely the right thing to do in a constructor. Saying not to do any real work in a destructor is just silly. It is there for a purpose. To guarantee that certain tasks are carried out, without relying on the programmer to explicitly call cleanup functions.
jalf
@jalf: In *this situation* it is plainly a mistake. Role of the pthread_join call is to make sure thread executing Thread::routine() is finished before Thread object is destroyed. But if it's called in Thread's destructor it is simply too late. If thread is still running, it is already using partially destructed object (MyThread's destructor already finished). You can avoid this problem by putting pointer to thread object in some kind of guard object and call Thread::join() i guard's destructor (RAII pattern).
Tomek Szpakowicz
Indeed, what I decided to do is make Thread have no constructors or destructors, instead having void beginThread(), void endThread(), and virtual void onThread(). Subclasses are then expected to call the appropriate functions in their own constructors and destructors. Moreover, it seems quite elegant when used as a mixin.
Joey Adams
@Joey Adams: I don't really understand your new design.Aren't you just making this over-complicated?Mixins, etc...Anyway, here are some general comments:Thread is polymorphic class and is intended to be used as a base class so: a) you should define a virtual destructor, even if empty (some compilers will warn you if you don't), b) you should declare *private* operator= and copy constructor (so they are not used by mistake). I'd also explicitly provide Thread::Thread() but it isn't strictly necessary.
Tomek Szpakowicz
(b) isn't because it's a base class, btw, it's because you can't call join twice on the same `pthread_t`, and that's what would happen if you copied a Thread object. There's a bit more to the argument of whether base classes should permit slicing in general - some should, some shouldn't.
Steve Jessop
@Steve: b) is exactly because it's a base class.In my opinion possibility of slicing (and being it a default behaviour) is a bug in the C++ language.I cannot imagine any really useful use for it.If your design relies on slicing you should probably rethink it.
Tomek Szpakowicz
Well, that's fine as your opinion, you don't have to use that feature. Others have found ways to use it - a slice is not inherently different from any other implicit conversion in C++, and it's only bad if you're used to polymorphic pointers or references, and hence don't expect the slice on a pass-by-value. As I say, there's an argument to be had whether it's the responsibility of base classes to prevent slicing - in your coding standard clearly it is, in other coding standards (including mine) it isn't - I just say that if you don't want implicit conversion, don't pass by value.
Steve Jessop
Either way, though, my other point is that whether Thread is a base class or not, it's not copyable. That's because it owns resources which it disposes in its destructor and which cannot be copied.
Steve Jessop
@Steve: "It's not a bug. It's a _feature_."
Tomek Szpakowicz
If you want all your base classes to be polymorphic, you can always write Java instead of C++. If you're interested in pass-by-value, then you need a means to convert between types. So yes, it's a feature: C++ allows different projects to use various different programming techniques. Your rule is probably fine for particular projects, but it's not the responsibility of all C++ programmers to make C++ more Java-like, for people who'd rather be writing Java ;-)
Steve Jessop