Передав функцию-член в pthread_create


Я должен использовать функции-члена C++ в pthread_create. Кроме того, функцию-член должен быть в состоянии сделать то аргумента.

Итак, я реализовал функцию запуска, который с помощью пусковой объект, который хранит адрес функции-члена и аргумент, сможете сделать желаемое.

Существует ли лучше/элегантный способ сделать это?

template <class T>
class Launcher
{
public:
    Launcher(T * obj, void * (T::*mfpt) (void *), void * arg) : myobj(obj), fpt(mfpt), myarg(arg) {}
    ~Launcher() {}

    void launch() { (*myobj.*fpt)(myarg);}
private:
    T* myobj;
    void* myarg;
    void * (T::*fpt) (void *); //Member function pointer 
};

template <class T>
void * LaunchMemberFunction(void * obj)
{
    Launcher<T> * l = reinterpret_cast<Launcher<T>*>(obj);
    l->launch();
} 

Полную программу и использования ниже: Ссылка на код

#include <iostream>
#include <pthread.h>

class ScopedLock
{
public:
    ScopedLock(pthread_mutex_t& mutex);
    ~ScopedLock();

    void lock();
    void unlock();
    bool isLocked();

private:
    pthread_mutex_t& _mutex;
    bool _locked;
};

ScopedLock::ScopedLock(pthread_mutex_t& mutex) : _mutex(mutex) // references can only be initialized here
{
    //pthread_mutex_init(&_mutex, NULL);
    //_mutex = mutex;
    pthread_mutex_lock(&_mutex);
    std::cout << "acquired mutex\n";
    _locked = true;
}

ScopedLock::~ScopedLock()
{
    pthread_mutex_unlock(&_mutex);
    std::cout << "destroyed mutex\n";
    _locked = false;
}

void ScopedLock::lock()
{
    if (_locked) return;

    pthread_mutex_lock(&_mutex);
    _locked = true;
}

void ScopedLock::unlock()
{
    if (!_locked) return;
    pthread_mutex_unlock(&_mutex);
    _locked = false;
}

bool ScopedLock::isLocked()
{
    return _locked;
}

int count = 0;

class Test
{
public:
    Test() { pthread_mutex_init(&new_mutex, NULL); }
    ~Test() {}

    void * compute(void * data);
private:
    pthread_mutex_t new_mutex;
};

void * Test::compute(void * data)
{
    int id = (int)data;
    std::cout << "my thread id:" << id << "|" << pthread_self() <<
        std::endl;
    std::cout << "before mutex count = " << count << "|" << id << std::endl;
    ScopedLock sl(new_mutex);
    sleep(3);
    std::cout << "after sleep mutex count = " << count << "|" << id << std::endl;
    ++count;
    std::cout << "after Inc mutex count = " << count << "|" << id << std::endl;
}

template <class T>
class Launcher
{
public:
    Launcher(T * obj, void * (T::*mfpt) (void *), void * arg) : myobj(obj), fpt(mfpt), myarg(arg) {}
    ~Launcher() {}

    void launch() { (*myobj.*fpt)(myarg);}
private:
    T* myobj;
    void* myarg;
    void * (T::*fpt) (void *); //Member function pointer 
};

template <class T>
void * LaunchMemberFunction(void * obj)
{
    Launcher<T> * l = reinterpret_cast<Launcher<T>*>(obj);
    l->launch();
}

int main()
{
    pthread_t threads[3];
    Launcher<Test> * larray[3];
    Test t;
    for (int i = 0; i < 3; ++i)
    {
        std::cout << "In main: creating thread " << i << std::endl;

        larray[i] = new Launcher<Test>(&t, &Test::compute, (void*)i);
        std::cout << "address of l is " << larray[i] << std::endl;
        int rc = pthread_create(&threads[i], NULL, LaunchMemberFunction<Test>, larray[i]);
        if (rc)
        {
            std::cout << "Error: return code from pthread_create() is "
                      << rc << " i= " << i << "\n";
            exit(-1);
        }
    }

    for (int i = 0; i < 3; ++i)
    {
        pthread_join(threads[i], NULL);
    }

    std::cout << "main thread count = " << count << std::endl;

    //delete [] larray;
    for (int i = 0; i < 3; ++i)
    {
        delete larray[i]; //cannot do delete [] larray here because larray has not been created by new
    }

    pthread_exit(NULL);
}


5365
2
задан 4 сентября 2011 в 10:09 Источник Поделиться
Комментарии
2 ответа

Несколько случайных точек:

Я нахожу это немного режет слух, что лаунчер имеет т* в качестве члена. В целом при написании/обзор C или C++ я хотел бы быть в состоянии быстро ответить на вопросы типа "кто выпускает этот объект, и когда?" Путем проведения потенциально-оборванных Т* кстати, что вы делаете, что право собственности на ресурсы становится неоднозначным и обработка удаления объектов безопасным способом становится сложнее, когда вы могли еще что-нить с ним работать. Я хотел бы рассмотреть с подсчетом ссылок, умный указатель, как shared_ptr.

Это также немного странно, что лаунчер привязан к способу указатели. Для большей обобщенностью может лаунчер просто должен быть шаблон параметр, который обрабатывает оператор (), то можно получить несколько реализаций для этого, в том числе один, который вызывает метод указатель. Может, что-то вроде этого:

template <class T>
void *thread_creation_routine(void *ptr)
{
// This thread now owns deleting the object...
// See pthread_create call below.
//
auto_ptr<T> arg(reinterpret_cast<T*>(ptr));
return (*arg)();
}

Тогда вы могли бы вызвать его примерно так:

template <class T>
class Launcher
{
shared_ptr<T> m_ptr;
void *(T::*m_func)();
public:
Launcher(shared_ptr<T> &ptr, void *(T::*func)()) : m_ptr(ptr), m_func(func) {}

void *operator ()()
{
return (*m_ptr.*m_func)();
}
};

Обратите внимание в этом использование, лаунчер только один функтор, который может быть передан thread_creation_routine. Вы можете представить себе подобное в будущем. (Скажем, в C++0х лямбда-выражения, хотя в тот момент я бы сказал, что, возможно, вам следует заглянуть в новую тему то, что в C++0х.) Тогда, наконец, вы бы связать все это вместе с чем-то вроде этого:

class MyDemo
{
public:
void *SayHello()
{
cout << "Hello, threaded world!" << endl;

return 0;
}
};

int main()
{
shared_ptr<MyDemo> myDemo(new MyDemo());
Launcher<MyDemo> *myLauncher(new Launcher<MyDemo>(myDemo, &MyDemo::SayHello));

pthread_t t;
int r;

if ((r=pthread_create(&t,
NULL,
thread_creation_routine<Launcher<MyDemo> >,
myLauncher)))
{
// Failed to create the thread...
// Thread_creation_routine cannot take ownership of the launcher, so
// we delete it here.
//
delete myLauncher;

cerr << "pthread_create failed with error " << r <<
" (" << strerror(r) << ")" << endl;
return r;
}

// NB: this could theoretically fail; error checking omitted...
//
pthread_join(t, NULL);

return 0;
}

Отметим только странно, что здесь право собственности myLauncherс распределения. Если мы преуспели в создании потока, поток рутинных удаляет его, иначе главным будет. Чтобы сделать такого рода вещи исключений было бы желательно, чтобы обернуть, что выделение в РАИИ, но в этом примере он не нужен.

Затем обратите внимание самый быстрый переписанный лаунчер, вы можете на самом деле не метод, указатель вызова, полностью сохраняя при этом большое исходный код, изменять или повторно использовать нить процедуре создания:

class Launcher2
{
shared_ptr<MyDemo> m_ptr;
public:
Launcher2(shared_ptr<MyDemo> &ptr) : m_ptr(ptr) {}

void *operator ()()
{
return m_ptr->SayHello();
}
};

Вы можете представить себе компилятор, генерирующий эффективный код при использовании этого лаунчера. (Например. вызов метода может быть встроен в создание потока рутины. Кроме того, указатель на функцию вызывает, как правило, не так эффективно на современных процессорах, как просто вызов функции.) В большинстве случаев это не будет иметь значения, но если у вас очень частые звонки на подобное мероприятие, это хорошая вещь, чтобы быть в курсе в целом, и одним из реальных преимуществ с++. Вы можете также представить дополнительные пусковые установки, которые носить параметры для вызываемого абонента, аккуратно освобождая любые полученные ресурсы они будут удалены.

Также: для вашего уровня замки... это редкость, но pthread_mutex_lock может потерпеть неудачу. Я не уверен, что с верхней части моей головы, что эти условия отказ может быть, но вы могли бы хотеть сделать что-то для таком случае, как ошибка.

И наконец... я бы повторять ранее комментарий, что, возможно, поддержку C++0х нить является правильным для вас. (Честно говоря, я не смотрел в его все, что много, поэтому я многого не знаю об этом сам.) В противном случае, возможно увеличить.

3
ответ дан 4 сентября 2011 в 11:09 Источник Поделиться

Технически pthreads не является библиотекой Си.
Таким образом, функция обратного вызова передается в pthread_create() должна быть функцией с с компоновкой. Для этого в код C++ функция должна быть объявлена как extern "с".

Так это может работать, но это не портативный, и вы просто случайно повезло, что компилятор использует те же соглашения о вызовах для функции C, как для функций c++. Но ведь в C++ Аби намеренно не определяется, это может не для всех компиляторов.

Когда вы находитесь в вашей функции это совершенно законно, чтобы использовать все трюки с++.

Мне нравится лично вам нравится использовать оператора reinterpret_cast<>() , чтобы преобразовать указатель обратно к своему исходному типу. Но я считаю, что я в меньшинстве на этом и большинство людей использовать static_cast<>(). Там были несколько длинных дискуссий о использовании (есть чек на детали).

Некоторые незначительные изменения, чтобы сделать его законным:

class LauncherBase
{
public:
virtual ~Launcher() {}

virtual void launch() = 0;
};

template <class T>
class Launcher: public LauncherBase
....

extern "C" void* LaunchMemberFunction(void* obj);

void* LaunchMemberFunction(void* obj)
{
Launcher<T> * l = reinterpret_cast<LauncherBase*>(obj);
l->launch();
// should always return something from a non void function
return NULL; // I would make launch return the result.
}

int main()
{
....
int rc = pthread_create(&threads[i],
NULL,
LaunchMemberFunction,
dynamic_cast<LauncherBase*>(larray[i])
);
....
}

Ваш Scopped замки messey.

Почему есть механическая блокировка()/разблокировать() это приведет к проблемам, если правильно обработаны. Если вам нужен способ, чтобы временно разблокировать заблокированный мьютекс, то вы должны иметь класс scopped_unlock, который обрабатывает отпирания/запирания симметрично.

Здесь:

void ScopedLock::unlock()
{
if (!_locked) return;
pthread_mutex_unlock(&_mutex);
_locked = false;
}

Вы вводите потенциальной гонки как _locked изменяется вне рамок под запертой.

1
ответ дан 4 сентября 2011 в 11:09 Источник Поделиться