【问题标题】:C++ Timer - Start & Stop works - Restart doesn'tC++ 计时器 - 启动和停止工作 - 重新启动不
【发布时间】:2023-01-31 17:18:00
【问题描述】:

我在使用重新启动函数退出线程时遇到问题。当调用 Stop 时它退出线程,但是 Restart 调用 Stop 然后在之后立即开始 - 不退出线程 - >调用 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()时,您如何知道线程何时结束?在你开始新的之前,你怎么知道它已经结束了?如果两个线程同时运行,哪个线程收到条件?可以肯定的是,你不应该detach它,这样你就可以在Stop()join它。
  • 除此之外,什么会阻止某人做Start(); Start();?它提出从一开始就存在重大的设计问题。
  • @WhozCraig 这只是代码的虚拟版本。我明白了,不用担心 ;) 但还是谢谢
  • @Scheff'sCat 我添加了 if joinable() then join() 并且它输出了预期的输出,但是在那之后因为“没有活动异常而终止调用”而立即崩溃
  • 旁注:关于using namespace std...

标签: c++ multithreading timer restart conditional-variable


【解决方案1】:

您以这种方式设计了线程 – stop 终止了线程,此时线程消失了 – start 创建了一个新线程,并且不能做任何其他事情,因为另一个丢失了。

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

你已经在你的线程中包含了一个循环——现在确保你这样做了不是如果您要重新启动线程,请退出此循环。您目前在您的代码中包含了一个 return – 这使得 running 在您的 while 循环中对 running 的测试无论如何都过时了。我们现在可以稍微改变一下,将 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();
}

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

【讨论】:

  • 谢谢!在您的帮助和解释下设法让它工作。 :-)
最近更新 更多