Вызов detach() в конце потока

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

Я попытался вызвать detach в конце потока, и он отлично работает. Безопасно ли вызывать detach в конце потока? Или как я могу безопасно создать следующий рабочий поток, не вызывая end_work перед вызовом begin_work?

class thread_worker {
private:
    std::thread worker;
    // ... other menbers

public:
    thread_worker() {};
    ~thread_worker() { end_work(); };

    void begin_work() {
        if (!worker.joinable()) {
            worker = std::thread { &thread_worker::do_work, this };
        }
    }

    void do_work() {
        // ... access other members ...

        if (exit not by notify) {
            worker.detach();    // can I call detach?
        }
    }

    void end_work() {
        if (worker.joinable()) {
            // notify worker to exit
            worker.join();
        } 
    }
};

Изменить:

Моя цель - вызвать begin_work без блока. Если при выполнении есть один рабочий поток, то функция вернется напрямую или вернет ошибку is_working. В противном случае легко создайте новый рабочий поток.

Поскольку std::thread::joinable() всегда возвращает true, пока не будет вызван join или detach. В результате будущий вызов begin_work никогда не создаст новый рабочий поток, даже если текущий рабочий поток завершился.

Поэтому мне нужен механизм автоматического отсоединения в конце потока.


person Kai    schedule 17.07.2020    source источник
comment
std::thread не является потокобезопасным, почему бы просто не вызвать end_work() в начале begin_work?   -  person Alan Birtles    schedule 17.07.2020
comment
Так как begin_work неет сохранять текущий рабочий поток при его запуске. end_work вызывается только тогда, когда пользователь выходит из приложения.   -  person Kai    schedule 17.07.2020
comment
@Kai std::thread::join не завершает текущий рабочий поток. Он будет держать его и ждать, пока он не закончится.   -  person Daniel Langr    schedule 17.07.2020
comment
@ Кай, хм ... разве ты не говорил, что begin_work создает новый поток только тогда, когда предыдущий поток закрывается? Итак, как можно создать дополнительные потоки? Если вы намерены вызвать end_work в конце выхода из приложения, вы, вероятно, захотите, чтобы несколько потоков выполнялись одновременно.   -  person P.P    schedule 17.07.2020
comment
Мне кажется, это проблема XY. Я бы рекомендовал описать, что вы действительно пытаетесь решить, определив класс thread_worker. Какова его цель? Как его предполагается использовать?   -  person Daniel Langr    schedule 17.07.2020
comment
Чтобы добавить к тому, что сказал @DanielLangr, отсоединение потока не приводит к его закрытию. Да, вы можете вызвать detach(), но поток продолжит работу.   -  person Pete Becker    schedule 17.07.2020
comment
@Daniel Поскольку в end_work он уведомляет текущий поток о выходе перед вызовом join(), текущий рабочий поток будет немедленно прерван при получении уведомления о выходе. Если я вызову begin_work несколько раз, можно будет выполнить только последний рабочий поток.   -  person Kai    schedule 17.07.2020
comment
@Kai Кай, я обновил свой ответ на основе твоего редактирования.   -  person P.P    schedule 17.07.2020
comment
@PP Спасибо за ваш ответ. Я думаю, что w_done нужно инициализировать до true. В противном случае рабочая нить никогда не начнется.   -  person Kai    schedule 20.07.2020


Ответы (1)


Я попытался вызвать detach в конце потока, и он отлично работает.

При доступе к worker происходит гонка данных — это поведение undefined. Когда begin_work проверяет worker.joinable(), do_work может одновременно отсоединять его (вызов worker.detach()).

Вместо этого вы можете сразу же отсоединить его при его создании:

worker = std::thread { &thread_worker::do_work, this };
worker.detach();

Однако это может привести к одновременному запуску нескольких потоков, что противоречит вашему требованию запускать один рабочий поток за раз (но почему только один? Это просто делает многопоточность бессмысленной).

Вместо этого вы можете сделать:

void begin_work() {
    end_work();
    worker = std::thread { &thread_worker::do_work, this };
} 

что гарантирует завершение предыдущего потока.


Основываясь на вашем редактировании, вам нужно только проверить, можете ли вы присоединиться без ожидания — это, кажется, причина, по которой вы хотите отсоединиться. Вместо этого вы можете сделать это с атомарным флагом. По сути, вы просто должны позаботиться об гонке данных, упомянутой выше.

class thread_worker {
private:
    std::thread worker;
    std::atomic_bool w_done {true};
    
    // ... other menbers

public:
    thread_worker() {};
    ~thread_worker() { end_work(); };

    void begin_work() {
        if (w_done) {
            end_work();   
            worker = std::thread { &thread_worker::do_work, this };
        }
    }

    void do_work() {
        // ... access other members ...

        w_done = true;
    }

    void end_work() {
        w_done = false;
        if (worker.joinable()) {
            // notify worker to exit
            worker.join();
        } 
    }
};
person P.P    schedule 17.07.2020