охрана темы против scoped_thread - PullRequest
7 голосов
/ 07 июня 2019

В книге

«Параллелизм C ++ в действии» Энтони Уильямса

вы можете найти следующие два фрагмента кода (я внес несколько небольших изменений):

Фрагмент 1:

class thread_guard
{
    std::thread& t;
    public:
    explicit thread_guard(std::thread& t_): t(t_){}
    ~thread_guard()
    {
        if(t.joinable())
    {
        t.join();
    }
    }
    thread_guard(thread_guard const&)=delete;
    thread_guard& operator=(thread_guard const&)=delete;
};

void my_func()
{
    for(int j = 0; j < 1000; ++j)
    {
        cout << "\n " << j;
    }
}

void f()
{
    std::thread t1(my_func);
    thread_guard g(t1);
    do_something_in_current_thread();
}

int main()
{
    f();
    return 0;
}

Продолжая, вы можете найти

Фрагмент 2:

class scoped_thread
{
    std::thread t;
    public:
    explicit scoped_thread(std::thread t_):    t(std::move(t_))
    {
        if(!t.joinable())
        throw std::logic_error(“No thread”);
    }
    ~scoped_thread()
    {
        t.join();
    }
    scoped_thread(scoped_thread const&)=delete;
    scoped_thread& operator=(scoped_thread const&)=delete;
};

void my_func()
{
    for(int j = 0; j < 1000; ++j)
    {
        cout << "\n " << j;
    }
}

void f()
{
   scoped_thread st1(thread(my_func));

   thread t2(my_func);
   scoped_thread st2(move(t2));

   do_something_in_current_thread();
}

int main()
{
    f();
    return 0;
}

Я не уверен, что могу по-настоящему оценить реальную разницу между этими двумя фрагментами.

Единственное отличие, которое я вижу, состоит в том, что в Snippet 1 экземпляр thread_guard не становится владельцем потока t1 (в отличие от scoped_thread объекта), и поэтому возможен вызов t1.join(), но это не проблема при выполнении ~thread_guard().

Итак: где (если существует) преимущество Snippet 2?

Ответы [ 2 ]

5 голосов
/ 07 июня 2019

Оба типа предназначены для блокировки при разрушении (например, при выходе из области действия) до завершения потока.Разница заключается во владении объектом thread.

thread_guard не владеет самим thread;на одном и том же thread может быть более одного thread_guard.Это также означает, что объект thread должен быть живым до тех пор, пока любой thread_guard ссылается на него.Если указанный поток уже был присоединен при уничтожении объекта thread_guard, он не будет блокировать или выдавать ошибку (в отличие от простого вызова join в потоке, который нельзя присоединить).

scoped_thread, с другой стороны, становится владельцем экземпляра thread и, следовательно, также контролирует его время жизни.Вы будете использовать его всякий раз, когда захотите владеть потоком, от которого хотите ждать, например, в качестве члена данных.

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

0 голосов
/ 07 июня 2019

С точки зрения функциональных возможностей, обе эти реализации способны обслуживать цели, единственное отличие, которое я вижу в этих двух реализациях, заключается в том, что Snippet 2 может принимать как lvalue(glvalue), так и rvalue(prvalue), но Snippet 1 не может принятьrvalue(prvalue) в качестве аргумента конструктора.Например, рассмотрим следующий код:

std::thread getThread()
{
    return std::thread([](){ std::cout<< __PRETTY_FUNCTION__<< std::endl;});
}

int main( int , char *[])
{
    thread_guard g( getThread());
    return 0;
}

Теперь, если вы скомпилируете этот код, компиляция выдаст следующую ошибку,

 error: cannot bind non-const lvalue reference of type ‘std::thread&’ to an rvalue of type ‘std::remove_reference<std::thread&>::type’ {aka ‘std::thread’}
     explicit thread_guard(std::thread _t): t(std::move( _t)){ std::cout<< __PRETTY_FUNCTION__<< std::endl;}

Но реализация snippet 2 будет работать нормально.

...