приведение к void* для передачи объектов в pthread в c++

Я немного не понимаю, как передать объект функции pthread_create. Я нашел много разрозненной информации о приведении к типу void*, передаче аргументов в pthread_create и т. д., но ничего, что связывало бы все это воедино. Я просто хочу убедиться, что связал все это вместе и не делаю глупостей. Допустим, у меня есть следующий класс потока: Изменить: исправлено несоответствие static_cast.

class ProducerThread {
    pthread_t thread;
    pthread_attr_t thread_attr;
    ProducerThread(const ProducerThread& x);
    ProducerThread& operator= (const ProducerThread& x);
    virtual void *thread_routine(void *arg) {
        ProtectedBuffer<int> *buffer = static_cast<ProtectedBuffer<int> *> arg;
        int randomdata;

        while(1) {
            randomdata = RandomDataGen();
            buffer->push_back(randomdata);
        }

        pthread_exit();
    }
public:
    ProtectedBuffer<int> buffer;

    ProducerThread() {
        int err_chk;

        pthread_attr_init(&thread_attr);
        pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_DETACHED);

        err_chk = pthread_create(&thread, &thread_attr, thread_routine, static_cast<void *> arg);
        if (err_chk != 0) {
            throw ThreadException(err_chk);
        }
    }
    ~ProducerThread() {
        pthread_cancel(&thread);
        pthread_attr_destroy(&thread_attr);
    }
}

Чтобы уточнить, к данным в классе ProtectedBuffer можно получить доступ только с помощью таких методов, как ProtectedBuffer::push_back(int arg), которые используют мьютексы для защиты фактических данных.

Мой главный вопрос: правильно ли я использую static_cast? И мой второстепенный вопрос: нужна ли мне эта первая строка в virtual void *thread_routine(void *arg), где я копирую переданный указатель void в указатель на ProtectedBuffer?

Кроме того, если я сделал что-то еще, что может вызвать проблемы, я был бы признателен за это.


person Beezum    schedule 08.08.2012    source источник
comment
Эм, вы не можете передавать функции-члены в pthread_create, не так ли? (Не связано: зачем кому-то нужен базовый класс потока в С++? А полиморфные базовые классы в С++ должны иметь виртуальные деструкторы)   -  person R. Martinho Fernandes    schedule 08.08.2012
comment
@R.MartinhoFernandes: Нет, он должен быть статичным. ( this класса потока может быть аргументом процедуры потока, предоставляя состояние для процедуры потока. Согласовано.)   -  person jxh    schedule 08.08.2012
comment
Посмотрите на это: stackoverflow.com /вопросы/8920441/   -  person PiotrNycz    schedule 08.08.2012
comment
@ user315052 Это не может быть участником, и точка. Даже статический. Согласно стандарту C++, это должно быть extern "C", и любая спецификация компоновки игнорируется в функциях-членах. (Стандарт требует диагностики, но есть некоторые сломанные компиляторы, которые не могут обнаружить ошибку.)   -  person James Kanze    schedule 08.08.2012
comment
@ mathematician1975: бесконечный цикл был просто для иллюстрации того, что может сделать поток. На самом деле это будет опрос последовательного порта и анализ данных, но я еще не реализовал этот бит. Кроме того, основной поток будет постоянно очищать буфер с другого конца.   -  person Beezum    schedule 08.08.2012
comment
@PiotrNycz: спасибо. Эта ветка многое проясняет.   -  person Beezum    schedule 08.08.2012
comment
@JamesKanze: Пожалуйста, смотрите мои публичные извинения в моем ответе. С Уважением   -  person jxh    schedule 09.08.2012


Ответы (2)


Если вы хотите пойти по этому пути, я считаю, что вам нужно что-то вроде этого:

Редактировать. Основываясь на ответе Джеймса Канце, добавьте отдельный метод activate для запуска потока после завершения построения.

class GenericThread {
protected:
    GenericThread () {
      //...
    }
    virtual ~GenericThread () {}

    int activate () {
        return pthread_create(..., GenericThreadEntry, this);
    }

    virtual void * thread_routine () = 0;

    #if 0
    // This code is wrong, because the C routine callback will do so using the
    // C ABI, but there is no guarantee that the C++ ABI for static class methods
    // is the same as the C ABI.
    static void * thread_entry (void *arg) {
        GenericThread *t = static_cast<GenericThread *>(arg);
        return t->thread_routine();
    }
    #endif
};

extern "C" void * GenericThreadEntry (void *) {
    GenericThread *t = static_cast<GenericThread *>(arg);
    return t->thread_routine();
}

Тогда ProducerThread будет происходить от GenericThread.

Изменить: Поиск extern "C" в стандарте C++. не обнаружил требования, чтобы указатель на функцию указывал на функцию со связью C, чтобы ее можно было вызвать подпрограммой библиотеки C. Поскольку передаются указатели, требования к связыванию не применяются, поскольку связывание используется для разрешения имен. Указатель на статический метод является указателем на функцию в соответствии с проектом C++ 2011 (n3242), Sec. 3.9.2п3:

За исключением указателей на статические члены, текст, относящийся к указателям, не применяется к указателям на члены.

Правка: Вина. Библиотека C вызовет функцию обратного вызова, принимая во внимание двоичный интерфейс приложения C. Функция со связью C++ может использовать ABI, отличный от C ABI. Вот почему необходимо использовать функцию со связью extern "C" при переходе к функции обратного вызова в библиотеку C. Мои искренние извинения Джеймсу Канзе за то, что я сомневался в нем, и моя искренняя благодарность Локи Астари за то, что он поставил меня на место.

person jxh    schedule 08.08.2012
comment
Пожалуйста, оставьте комментарий при голосовании, чтобы я знал, как улучшить ответ. Спасибо! - person jxh; 09.08.2012
comment
голосование против без комментариев настолько распространено: meta.stackexchange.com/a/22935/138817, что такие запросы пустая трата времени. Если бы человек, который проголосовал за вас, хотел оставить комментарий, он бы уже это сделал. - person Martin York; 09.08.2012

Есть ряд проблем с вашим кодом. Во-первых, я не вижу, где объявлен arg, который вы выполняете, поэтому я не могу сказать, подходит ли этот случай.

Возможно, что более важно, thread_routine является функцией-членом, поэтому ее нельзя преобразовать в указатель на функцию. Функция, переданная pthread_create, должна быть extern "C", поэтому она не может быть членом, точка; это должна быть свободная функция, объявляющая extern "C". Если вы хотите вызвать функцию-член, передайте указатель на объект в качестве последнего аргумента и разыменуйте его в функции extern "C":

extern "C" void* startProducerThread( void* arg )
{
    return static_cast<ProducerThread*>( arg )->thread_routine();
}

И для начала темы:

int status = pthread_create( &thread, &thread_attr, startProducerThread, this );

Только не делайте этого в конструкторе. Другой поток может начать работу до того, как объект будет полностью построен, что приведет к катастрофическим последствиям.

Кроме того, убедитесь, что приведение в startProducerThread имеет точно тот же тип, что и указатель, переданный в pthread_create. Если вы выполняете приведение к базовому классу в startProducerThread, то будьте очень, очень уверены, что это указатель на этот базовый класс, который вы передаете в pthread_create; при необходимости используйте явное приведение (к типу startProducerThread, не к void*).

Наконец, хотя это и не относится к вашему фактическому вопросу: если ProtectedBuffer имеет интерфейс, подобный интерфейсу std::vector, и возвращает ссылки на внутренние данные, вы не сможете сделать его потокобезопасным. Защита должна быть внешней по отношению к классу.

person James Kanze    schedule 08.08.2012
comment
Хороший вопрос об активации в конструкторе, я исправлю свой ответ. - person jxh; 08.08.2012
comment
ProtectedBuffer фактически использует контейнер deque внутри. Это было предметом предыдущего вопроса, который я задал на SO (stackoverflow.com/questions/11640681/). Как я могу убедиться, что функции-члены возвращают копии данных в ProtectedBuffer, а не ссылки? - person Beezum; 08.08.2012