【问题标题】:Threaded timer, interrupting a sleep (stopping it)线程定时器,中断睡眠(停止它)
【发布时间】:2015-09-21 10:37:07
【问题描述】:

我想要一个相当可靠的线程计时器,所以我编写了一个计时器对象,它在线程上触发 std::function。我想让这个计时器能够在它到达下一个刻度之前停止;你不能用 ::sleep 做的事情(至少我不认为你可以)。

所以我所做的就是在互斥体上放置一个条件变量。如果条件超时,我会触发该事件。如果条件发出信号,则线程退出。所以 Stop 方法需要能够让线程停止和/或中断它的等待,我认为这就是它现在正在做的事情。

然而,这存在一些问题。有时线程不是 joinable() ,有时条件是在超时之后但在进入等待状态之前发出信号。

我怎样才能改进它并使它变得健壮?

以下是完整的回购。此处的等待时间为 10 秒,但程序应在创建 Foo 时立即终止,然后立即销毁。有时会,但大多数情况下不会。

#include <atomic>
#include <thread>
#include <future>
#include <sstream>
#include <chrono>
#include <iostream>

class Timer
{
public:

    Timer() {}

    ~Timer()
    {           
        Stop();
    }

    void Start(std::chrono::milliseconds const & interval, std::function<void(void)> const & callback)
    {   
        Stop();

        thread = std::thread([=]()
        {
            for(;;)
            {
                auto locked = std::unique_lock<std::mutex>(mutex);
                auto result = terminate.wait_for(locked, interval);

                if (result == std::cv_status::timeout)
                {
                    callback();
                }
                else
                {
                    return;
                }
            }
        });
    }

    void Stop()
    {       
        terminate.notify_one();

        if(thread.joinable())
        {
            thread.join();
        }
    }

private:

    std::thread thread;
    std::mutex mutex;
    std::condition_variable terminate;
};

class Foo
{
public: 

    Foo()
    {
        timer = std::make_unique<Timer>();
        timer->Start(std::chrono::milliseconds(10000), std::bind(&Foo::Callback, this));
    }

    ~Foo()
    {

    }

    void Callback()
    {
        static int count = 0;

        std::ostringstream o;

        std::cout << count++ << std::endl;
    }

    std::unique_ptr<Timer> timer;
};


int main(void)
{
    {
        Foo foo;
    }

    return 0;
}

【问题讨论】:

  • 您需要一个受互斥体保护的变量,该变量存储线程是否应该停止。条件变量是无状态的——你有责任维护你正在等待的事物的状态(称为“谓词”)。基本上,您错过了条件变量的要点。您的 Stop 函数会通知线程。但它并没有改变线程正在等待的任何条件!请注意,互斥锁不保护任何东西。这是完全错误的。
  • 更新了代码,因为我错过了 else...!
  • 为什么return 会出现虚假唤醒? (同样的问题。您无法知道是否应该唤醒,因为您没有要检查的谓词。)
  • 还有哪些其他类型的唤醒?超时,用户发出等待信号。操作系统会因为其他原因唤醒它吗?
  • Ahhhhhhhh 我明白你现在对状态的意思了。我误解了 condition_variable 实际上在做什么。谢谢。

标签: c++ multithreading timer


【解决方案1】:

看我的评论。您忘记了实现线程正在等待的事物的状态,从而使互斥体没有任何东西可以保护,线程也没有任何东西可以等待。条件变量是无状态的——你的代码必须跟踪你要通知线程的事物的状态。

这是固定的代码。请注意,互斥锁保护stop,而stop 是线程正在等待的东西。

    class Timer
    {
    public:

        Timer() {}

        ~Timer()
        {           
            Stop();
        }

        void Start(std::chrono::milliseconds const & interval,
            std::function<void(void)> const & callback)
        {   
            Stop();

            {
                auto locked = std::unique_lock<std::mutex>(mutex);    
                stop = false;
            }

            thread = std::thread([=]()
            {
                auto locked = std::unique_lock<std::mutex>(mutex);    

                while (! stop) // We hold the mutex that protects stop
                {
                    auto result = terminate.wait_for(locked, interval);

                    if (result == std::cv_status::timeout)
                    {
                        callback();
                    }
                }
            });
        }

        void Stop()
        {    
            {     
                // Set the predicate
                auto locked = std::unique_lock<std::mutex>(mutex);
                stop = true;
            }

            // Tell the thread the predicate has changed
            terminate.notify_one();

            if(thread.joinable())
            {
                thread.join();
            }
        }

    private:

        bool stop; // This is the thing the thread is waiting for
        std::thread thread;
        std::mutex mutex;
        std::condition_variable terminate;
    };

【讨论】:

  • 我以前有这个,stop as std::atomic。我以为我需要互斥锁只是为了让 terminate 坐下来等待或超时。那么为什么我需要用互斥锁来保护 stop 呢?
  • @Robinson 避免在您输入wait_for 时更改状态的竞争条件。您需要互斥锁来保护谓词,这就是它被传递给wait_for 的原因。您缺少条件变量的全部意义,即提供原子的“解锁和等待”操作。
  • 哦,我明白了。我真傻。当然。谢谢。
  • 这是条件变量存在的全部原因。
  • 用你的代码我被“锁定”了......是一个被删除的函数,所以它不会编译。所以我通过引用将它传递给 capture [=, &locked] 并且它编译但我在 wait_for (OPERATION_NOT_PERMITTED) 处收到运行时错误。
猜你喜欢
  • 2011-05-14
  • 2015-06-28
  • 1970-01-01
  • 1970-01-01
  • 2019-11-29
  • 2013-06-12
  • 2019-05-24
  • 2012-08-08
  • 2012-12-22
相关资源
最近更新 更多