Thursday, December 29, 2011

Inspiration code - Final part

The last of these three parts summary is about _thread and Result structures.

_thread is a just a container for pthread_t and pthread_attr_t thread variables. It provides a wrapper for pthread_create and pthread_detach functions, and a member variable for storing the called function return value... However the most important task accomplished by this structure is reference counting.

template <typename T> struct _thread
{
    // This struct will hold the return value of the thread called func.
    
    _thread() : counter(0)
    {
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
    }

    ~_thread() { pthread_detach(thd); }

    T result;
    pthread_t thd;
    pthread_attr_t attr;
    volatile int counter;

    int join() { return pthread_join(thd, NULL); }
    int start(void*(f)(void*), void* v)
    {
        return pthread_create(&thd, &attr, f, v);
    }
    
    // Atomic function for add and sub
    int inc() { return __sync_fetch_and_add(&counter, 1); }
    int dec()
    {
        if (__sync_fetch_and_sub(&counter, 1) == 0)
            delete this;
        return counter;
    }

    private:
    _thread(const _thread&);
    _thread& operator=(const _thread&);
};

_thread has both inc/dec member functions that are called everytime the structure instance is referenced/dereferenced by some other object. When the dec member function is called and the counter becomes zero, then the destructor is invoked and _thread is deleted. Also, the destructor calls pthread_detach, so that thread's resources can be freed.

This reference counting mechanism is very important since we want that _help_fn and _help_st saves the computation results in a place from which we can fetch the result...

To clarify: we can't just launch the thread (that executes _help_fn), save the results and delete everything. We need to be sure that we are deleting the result at the right time.

This is why, when a new thread is created, the corresponding _thread object is referenced both by a _help_st and a Result object. In this way, at the end of _help_fn, when _help_st is deleted, the dec function of _thread object is called by _help_st destructor...
But _thread object is deleted only if also the corresponding Result object is already deleted.

template <typename T> struct Result
{

    Result(_thread<T>* thd) : thd(thd)
    {
        thd->inc();
    }

    Result(const Result<T>& o)
    {
        thd->inc();
        thd = o.thd;
    }

    Result<T>& operator=(const Result& o)
    {
        o.thd->inc();
        thd->dec();
        thd = o.thd;
        return *this;
    }

    T value()
    {
        switch (thd->join())
        {
            case 0:
              break;
            case EINVAL:
              throw std::runtime_error("EINVAL on pthread_join");
              break;
            case EDEADLK:
              throw std::runtime_error("EDEADLK on pthread_join");
              break;
            case ESRCH:
              // Thread already exited 
              break;
        }
        return thd->result;
    }

    ~Result()
    {
        thd->dec();
    }

    _thread<T>* thd;
}

The Result object is the one that is returned to the user who started a new thread. So, if the user keep it, he can fetch the return value through the Result object.
Otherwise the Result object is automatically destroyed and corresponding _thread object is deleted when dec is called by _help_st destructor.

To close this last part, here are the static methods of Thread class for creating a new thread that runs a function, a functor or an instance method having one argument. The signature is similar for functions, functors, and instance methods having more than one arument.
As you see the return value of these function is a Result object.


    template <typename T, typename O, typename I0> static Result<T>
    run(O obj, I0 a0)
    {
        _functor1<T, O, I0> o(obj, a0);
        return Result<T>(_start<T>(o));
    }

    template <typename T, typename I0> static Result<T>
    run(T(*fun)(I0), I0 a0)
    {
        _functor1<T, T(*)(I0), I0> f(fun, a0);
        return Result<T>(_start<T>(f));
    }

    template <typename T, typename C, typename I0> static Result<T>
    run(C* c, T(C::*fun)(I0), I0 a0)
    {
        _class_functor1<T, C, I0> f(c, fun, a0);
        return Result<T>(_start<T>(f));
    }

Returning Result object is assigned a _thread object, created by _start helper function in Thread class.


    template <typename T, typename F> static _thread<T>*
    _start(const F& functor)
    {
        _thread<T>* mythread = new _thread<T>();
        _help_st<T, F >* h2 = new _help_st<T, F>(mythread, functor);
        mythread->start(_help_fn<_help_st<T, F> >, h2);
        return mythread;
    }

I hope you enjoyed this post serie... If you have question or you need some clarification (maybe due to my poor english), just leave a comment. I will be happy to answer or rewrite the unclear part

No comments: