Как получить перемещенный указатель на функцию, которая выполняется в отдельном потоке? - PullRequest
1 голос
/ 16 января 2020

Как я могу получить указатель std::unique_ptr, который был перемещен в функцию, когда эта функция выполняется в отдельном потоке?

Рассмотрим следующий пример:

using IntPtr = std::unique_ptr<int>;

IntPtr&& update(IntPtr &&fp_)
{
    *fp_ = 23;
    return std::move(fp_);
}

int main()
{
    IntPtr foo_ptr = std::make_unique<int>(0);
    foo_ptr = update(std::move(foo_ptr));
    std::cout<< "data: " << *foo_ptr <<std::endl;
}

Это хорошо. foo_ptr перемещается в fp_ на update(), а затем извлекается (перемещается назад) в foo_ptr.

Как я могу получить foo_ptr, если update() работал на отдельном нить ???

using IntPtr = std::unique_ptr<int>;

IntPtr&& update(IntPtr &&fp_)
{
    *fp_ = 23;
    return std::move(fp_);
}

int main()
{
    IntPtr foo_ptr = std::make_unique<int>(0);
    std::thread foo_thread(update,std::move(foo_ptr));
    foo_thread.join();
    std::cout<< "data: " << *foo_ptr <<std::endl; // SEGFAULT because foo_ptr is a nullptr.
}

Пример кода онлайн: https://rextester.com/MQDVU97718

Ответы [ 2 ]

3 голосов
/ 16 января 2020

Нет.

Вы передали право собственности на память другому потоку. Этот поток теперь владеет этой памятью и будет удалять ее по завершении. Он не существует после join, и вы никогда не перемещали указатель обратно в основной поток.

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

Или просто используйте packaged_task<int(int)>, и вам не нужно (напрямую) выделять память в все:

int update(int value)
{
    return 23;
}

int main()
{
    int value = 0;
    std::packaged_task<int(int)> task(update);
    auto future_int = task.get_future();

    std::thread foo_thread(std::move(task), value);
    foo_thread.join();
    std::cout<< "data: " << future_int.get() <<std::endl;
}
2 голосов
/ 16 января 2020

Самый простой способ - использовать функцию-оболочку, которая записывает результат обратно в foo_ptr:

std::thread foo_thread([&foo_ptr]() {
    foo_ptr = update(std::move(foo_ptr));
});

foo_thread.join();
std::cout<< "data: " << *foo_ptr <<std::endl;

Обратите внимание, что между вызовом конструктора std::thread и завершением join() вызов, основной поток не должен иметь доступ к foo_ptr в любом случае. Это будет гонка данных. До конструктора и после join это нормально, так как оба этих вызова служат точками синхронизации, но любой доступ между ними будет несинхронизирован и будет выполняться с записью в foo_ptr внутри лямбды.

Если это звучит слишком опасно, рассмотрим подход, в котором поток помещает результат в std::promise, а основной поток затем получает объект из соответствующего std::future:

std::promise<IntPtr> p;
std::future<IntPtr> fut = p.get_future();
std::thread foo_thread([ptr = std::move(foo_ptr), p = std::move(p)]() mutable {
    p.set_value(update(std::move(ptr)));
});

foo_ptr = std::move(fut.get());
...