C++ 计时器 - 启动和停止有效 - 重新启动无效

标签 c++ multithreading timer restart conditional-variable

我在使用重新启动功能退出线程时遇到问题。当调用 Stop 时,它会退出线程,但是 Restart 会调用 Stop,然后立即调用 Start - 不会退出线程 -> 调用 Start 并创建一个新线程。

谢谢。任何帮助都会非常有帮助和感激。

显示问题的虚拟代码:

#include <iostream>
#include <thread>
#include <condition_variable>
#include <chrono>

using namespace std;

bool running = false;

unsigned int interval = 5000;

condition_variable cv_work;
mutex mu_cv_work;

void Start()
{
    unique_lock<std::mutex> lock(mu_cv_work);
    running = true;
    lock.unlock();
    thread([]{
        cout << "new thread" << '\n';
        while (running)
        {
            cout << "work..." << '\n';
            unique_lock<std::mutex> lock(mu_cv_work);
            cout << "sleep" << '\n';
            if (cv_work.wait_for(lock, chrono::milliseconds(interval), []{return running == false;}))
            {
                cout << "exit thread" << '\n';
                return;
            }
            cout << "done sleeping" << '\n';
        }
    }).detach();
}

void Stop()
{
    unique_lock<std::mutex> lock(mu_cv_work);
    running = false;
    lock.unlock();
    cv_work.notify_one();
}

void Restart()
{
    Stop();
    Start();
}

int main()
{
    Start();
    cout << "press to Stop" << '\n';
    cin.get();
    Stop();                             // Stop actually exits the Thread
    cout << "press to Start" << '\n';
    cin.get();
    Start();
    cout << "press to Restart" << '\n';
    cin.get();
    Restart();                         // Stop doesn't exit the Thread (Restart calls Stop() and Start())

    return 0;
}

输出:

press to Stop
new thread
work...
sleep
                      // KEY PRESS
exit thread
press to Start
                      // KEY PRESS
new thread
work...
sleep
press to Restart
                      // KEY PRESS
new thread
work...
sleep
done sleeping
work...
sleep

预期输出:

press to Stop
new thread
work...
sleep
                      // KEY PRESS
exit thread    
press to Start
                      // KEY PRESS
new thread
work...
sleep    
press to Restart
                      // KEY PRESS    
exit thread             // THIS LINE
new thread
work...
sleep
done sleeping
work...
sleep

最佳答案

您是这样设计线程的 – stop终止线程,此时它就消失了 – start创建一个新的,并且无法执行任何其他操作,因为另一个已丢失。

更糟糕的是:这两个线程(新的和旧的)可能会在执行中重叠一段时间。

您已经在线程中包含了一个循环 - 现在,如果要重新启动线程,请确保您退出此循环。您当前包含了 return在您的代码中 – 这使得 running running 的测试无论如何,在你的 while 循环中已经过时了。我们现在可以稍微改变一下,重用 running用于其他目的的变量;另外,我们可以使用三重状态,可能在枚举中定义:

enum ThreadState // ThreadCommand?
{
    Active,  // or Running, or Run, if named ...Command
             // choose whichever names appear most suitable to you...
    Restart,
    Exit,    // or Stop
};

现在您将状态设置为 Active启动线程时,停止时将状态设置为 Exit并在重新启动时按原样通知线程,您将适本地设置状态,再次通知线程。我个人会使用中间函数:

void stop()
{
    notify(Exit);
}
void restart()
{
    notify(Restart);
}
void notify(ThreadState state)
{
    // set the global state/command
    // notify the thread as done now
}

现在您将考虑循环中的状态:

[]()
{
    // endless loop, you return anyway...
    for(;;)
    {
        // working code as now
        // checking the notification as now, however the lambda now
        // checks the global state:
        if(wait_for(..., []() { return globalState != Active; })
        {
            if(globalState == Restart)
            {
                std::cout << "thread restarting" << std::endl;
                continue; // the loop!
            }
            else
            {
                std::cout << "thread exiting" << std::endl;
                return;
            }
        }
    }
}

但是请注意,您还应该重新初始化局部变量的状态(这是重新启动,不是吗?) - 至少只要这些不需要故意 在多个线程启动时持续存在。实现正确重新初始化的一个非常简单的方法可能是将变量打包到双循环中:

[]()
{
    // all variables of which the states should be persisted over multiple
    // starts – well, RE-starts only for now!!!
    // for all starts, you currently rely on global variables

    for(;;)
    {
        // all variables that should get initialised with every restart
        // they get destroyed and newly constructed with every loop run

        for(;;) // another nested loop...
        {
            // ...
            if(wait_for(/* as above*/))
            {
                if(globalState == Restart)
                {
                    break; // the INNER loop!
                           // we'd then continue the outer one
                           // achieving the re-initalisation
                }
                else
                {
                    return; // still terminating the thread
                }
            }
        }
    }
}

我仍然建议将所有这些代码打包到自己的类中。这有几个优点:

  • 您可以完全避免全局变量。
  • 您可以阻止访问用户不应直接访问的变量和函数(例如 globalState 变量和 notify 函数、互斥锁、条件变量以及可能的许多其他变量)。
  • 您可以并行启动多个线程,每个线程现在都有自己的一组变量:
class TaskRunner // (or whatever name suites you...)
{
public:
    // all of these: containing the code as above, but accessing member variables!
    start(); // the lambda would need to capture this now: [this]() { ... }!
    stop();
    restart();
private:
    enum ThreadCommand { /* ... */ }; // not of interest outside the class
    ThreadCommand m_command;
    // mutex, condition variable and whatever else is not of interest outside

    // I'd store the thread in a local variable as well!
    // the advantage of, is that you can check the tread
    // with `joinable`, though this means that you
    // either need to join the thread as well OR delay
    // detaching until calling stop
    std::thread m_thread;

    notify(ThreadCommand command)
    {
        m_command = command;
        // notify the thread runner
    }

    // I'd prefer an ordinary member function over the lambda:
    void run()
    {
        // with the double loop as above, though there might not be variables
        // any more persisted over re-starts only; you might instead entirely
        // rely on member variables...
    }
}

void ThreadRunner::start()
{
    if(m_thread.joinable())
    {
        // appropriate error handling...
    }
    else
    {
        // with separate run function instead of lambda:
        m_thread = std::thread(&ThreadRunner::run, this);
    }
}

void ThreadRunner::stop()
{
    // stopping thread as above

    if(m_thread.joinable())
    {
        m_thread.join(); // waits for thread completion, assuring some
                         // potential cleanup work correctly to be done
                         // prevents, too, two threads overlap provided
                         // subsequent `start` is not called from yet another
                         // thread (you might store thread id of last successful
                         // caller and check it to prevent such asituation)

        // alternatively you might detach, with all its disadvantages
        // but that's far less recommendable
    }
}

您应该调用stop也从析构函数内部确保线程正确停止,如果 ThreadRunner实例运行超出范围而没有 stop已被明确调用:

ThreadRunner::~ThreadRunner() // declare in class as well or define directly there
{
    stop();
}

注意:上面的任何代码都完全未经测试;如果您发现错误,请自行修复。

关于C++ 计时器 - 启动和停止有效 - 重新启动无效,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/73537371/

相关文章:

c# - 计时器延迟随着时间的推移而退化或变得不一致?

C++ - 通过网络发送 "Email Attachments"

c++ - 在 std::move() 之后使字符串为空的机制

c - Linux 中的线程同步?

java - 使用基于事件的计时器与轮询的性能注意事项

javascript - 如何创建在特定时间重置的倒计时时间

c++ - 将 Socket_t 指针传递给 ZeroMQ 发送函数

c++ - 如何使 DirectBuffer 次级声音?

java - 线程启动方法在运行返回之前返回

java - 多线程计时器无法正常工作