C ++ 11 shared_ptr и pthread - PullRequest
       7

C ++ 11 shared_ptr и pthread

0 голосов
/ 20 ноября 2018

У меня есть библиотека с API, использующими std::shared_ptr в качестве аргументов.

Я хотел бы использовать эти API вместе с pthreads.

Что я делаю: получение необработанного указателяиз shared_ptr, чтобы передать его в pthread.Создайте новый shared_ptr из исходного и вызовите мой API из другого потока.Однако я получаю ошибку double free or corruption при преобразовании необработанного указателя обратно в общий.

Это мой код

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

void* print_task(void* ptr) 
{
    int* val_raw = static_cast<int*>(ptr);
    std::shared_ptr<int> val(val_raw);

    // CALL MY API WHICH TAKES A SHARED_PTR AS ARGUMENT
    std::cout<<"thread job done \n";
}

int main(int argc, char ** argv)
{

   pthread_t thread;

   std::shared_ptr<int> val = std::make_shared<int>(10);
   pthread_create(&thread, nullptr, &print_task, static_cast<void *>(val.get()));

   std::this_thread::sleep_for(std::chrono::seconds(5));

   return 0;
}

Я предполагаю, что я делаю что-то не так со всемипреобразование из общего в необработанный указатель, потому что тот же код, использующий std :: threads (где я могу передать напрямую shared_ptr), работает.Однако мне нужно установить приоритеты потоков, поэтому я пытаюсь сделать это с помощью pthreads.

Знаете ли вы, как изменить мой код, чтобы можно было передавать общий указатель и использовать его внутри pthread?

Ответы [ 2 ]

0 голосов
/ 21 ноября 2018

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

Следовательно оба общие указатели пытаются удалить его.

У вас также есть неприятный проблема времени жизни , чтобы избежать, если вы передадите указатель на сам общий указатель, потому что вы не можете гарантировать, что общий указатель не выйдет из области видимости до того, как новый поток его скопирует.

Я получаю округлениечто при передаче секунды , динамически распределяемой std::shared_ptr, созданной из исходного std::shared_ptr, который гарантирует, что управляющий блок общего указателя будет жить, пока новый поток не попытается скопировать его.

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

void* print_task(void* ptr)
{
    // obtain the shared pointer from the dynamically created one guarantees
    // we will bee accessing a living control block preventing an
    // end-of-lifetime catastrophe
    std::shared_ptr<int> val = *static_cast<std::shared_ptr<int>*>(ptr);

    // DON'T FORGET TO DELETE THIS!!!
    // We had to allocate this dynamically to guarantee it lived until after
    // it was used.
    delete static_cast<std::shared_ptr<int>*>(ptr);

    // CALL MY API WHICH TAKES A SHARED_PTR AS ARGUMENT
    std::cout << "thread job done \n";

    return nullptr;
}

int main()
{
    pthread_t thread;

    // create the shared resource. This MAY go out of scope
    // before the new thread copies it (however unlikely you
    // think that is).
    std::shared_ptr<int> val = std::make_shared<int>(10);

    // So instead of sending the shared pointer we create a NEW std::shared_ptr
    // which will keep the shared pointer's control block alive even if the original
    // shared pointer goes out of scope.
    pthread_create(&thread, nullptr, &print_task,
        static_cast<void*>(new std::shared_ptr<int>(val)));

    // Do other time consuming thread stuff here.
    std::this_thread::sleep_for(std::chrono::seconds(2));


    // Tidy up.
    void* ret = nullptr;
    pthread_join(thread, &ret);

    return 0;
}

Конечно, теперь у вас есть пара new / delete, но вы все равно выигрываете в передаче общего владения между потоками.

Полагаю, вы даже можете избавитьсяиз этого delete путем принятия переданного в необработанном указателе временным 1034 * std::unique_ptr.

void* print_task(void* ptr)
{
    // obtain the shared pointer from the dynamically created one guarantees
    // we will bee accessing a living control block preventing an
    // end-of-lifetime catastrophe
    std::shared_ptr<int> val = *std::unique_ptr<std::shared_ptr<int>>(static_cast<std::shared_ptr<int>*>(ptr));

    // CALL MY API WHICH TAKES A SHARED_PTR AS ARGUMENT
    std::cout << "thread job done \n";

    return nullptr;
}
0 голосов
/ 21 ноября 2018

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

// this is what we have and what we want to pass to the given function
shared_ptr<some_type> sptr;

// function to somehow pass the shared pointer to
void function(void* ptr);

// As always, when passing anything that doesn't fit into
// the raw pointer, we need to do dynamic allocation:
void* arg = new shared_ptr<some_type>(sptr);

// we can now pass this to the function as intended:
function(arg);

// Note that we give up ownership of the dynamically allocated
// shared pointer instance. Hence, the called function must
// release that object again (it takes ownership). The function
// therefore starts like this:
void function(void* ptr)
{
    // convert the typeless pointer to a typed pointer again
    shared_ptr<some_type>* psptr = static_cast<shared_ptr<some_type>*>(ptr);
    // move the content to a new, local instance
    shared_ptr<some_type> sptr = *psptr;
    // release the dynamically allocated shared pointer again
    delete psptr;
    /// ... code using sptr here ...
}

Теперь, пока это гарантированноВ некоторых случаях это не может быть оптимальным решением:

  • Во-первых, взлеты и падения счетчика ссылок не бесплатны, тем более что это выполняется в поточно-ориентированном режиме,атомный путь.Можно избежать копирования общего указателя внутри функции, чтобы затем удалить скопированный указатель.Просто создайте пустой экземпляр и поменяйте его местами с указателем для копирования.Предполагая, что swap () является специализированным, что является безопасной ставкой, потому что это очевидная оптимизация, которая затем сводится к обмену двумя необработанными указателями.Этот обмен не должен быть потокобезопасным и, следовательно, намного быстрее.
  • Во-вторых, динамическое распределение стоит дорого.Вы можете избежать этого и ручного освобождения, передав адрес исходного объекта функции, но тогда вам придется гарантировать, что объект не будет затронут, пока функция еще выполняется.Особенно с нитями, которые требуют особой осторожности.
...