std :: thread访问此*的Unhanded异常

时间:2016-02-28 09:52:10

标签: c++ multithreading c++11

我有一个Timer类,它调用一个传递给create的函数,它循环并在任何时间内休眠。 我遇到的问题是当试图从传递的函数中杀死那个计时器时我在pThis->Stop()

获得了该例外

即使我访问全局类对象

,我似乎也遇到了同样的崩溃

类别:

class CTimer
{
public:
    CTimer()
        :_running(false)
    {}

    ~CTimer() {
        if (_running.load(std::memory_order_acquire)) {
            Stop();
        };
    }

    void Stop()
    {
        _running.store(false, std::memory_order_release);
        if (_thread.joinable())
            _thread.join();
    }

    template <typename F, typename... A>
    void Start(int interval, F func, A&&... args)
    {
        if (_running.load(std::memory_order_acquire))
            Stop();

        _running.store(true, std::memory_order_release);
        _thread = std::thread([this, interval, func, &args...]()
        {
            while (_running.load(std::memory_order_acquire))
            {
                func(this, std::forward<A>(args)...);

                std::this_thread::sleep_for(std::chrono::milliseconds(interval));
            }
        });
    }

    bool Running() const noexcept {
        return (_running.load(std::memory_order_acquire) &&
            _thread.joinable());
    }

private:
    std::atomic<bool> _running;
    std::thread _thread;
};

全局:

CTimer Timer;

主题:

void TimerThread(CTimer* pThis, HWND hwnd)
{
    // my code in side here
    // everything works fine till i try to stop within this thread


    // crash here
    pThis->Stop();
}

这样称呼:

Timer.Start(2000, TimerThread, hwnd);

3 个答案:

答案 0 :(得分:1)

你正试图从内部join()线程 - 这是一个问题。

看看你是否可以在滴答功能之外停止计时器。如果你不能,那么考虑让tick函数返回bool,看它是否应该继续运行。

无论如何,线程应始终加入,并始终来自不同的线程。

答案 1 :(得分:0)

我接受了代码并删除了hwnd - 替换为nullptr并找到了以下内容。

    if (_thread.joinable())
        _thread.join();

此代码在线程函数TimerThread中不起作用,因此当调用Stop时,会出现如cppreference : thread join所述抛出的死锁异常

我认为修复应该只是在函数中将_running设置为false。但目前尚不清楚代码中究竟需要什么。

其他说明

全局计时器破坏也可能发生得太晚 - 当全局变量被破坏时,这就像C ++运行时正在卸载一样,此时线程:: join可能会失败。

间隔将是更有效的计时机制,您可以在此之前测量时间,然后再读取它。

std :: chrono具有读取时钟和获取时序的方法。 cppreference: chrono

auto start = std::chrono::high_resolution_clock::now();
/* do some stuff */
auto end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = end-start;

答案 2 :(得分:0)

正如其他人所指出的那样,调用join()的线程本身就是一个保证死锁 - 尽管应该导致异常。您似乎是在Windows上开发 - 所以可能使用MSVC - 您可能想要检查您的库的标准合规性,因为直到最近它还没有。

这里还有其他一些非常糟糕的代码气味:

在析构函数中阻塞

目前CTimer的析构函数阻塞最多interval毫秒。这是一个糟糕的界面设计,因为它是违反直觉的。您可能很容易发现自己长时间阻塞主线程,而且如果func()阻塞资源 - 或者试图销毁CTimer对象,则非常有可能出现死锁。这是为将来存储的代码维护危险。

在析构函数中抛出异常

std::thread::join()可以抛出异常。这是std::terminate异常或未定义行为的快速路径。

在异步代码

中捕获原始this指针

就是不要这样做

虽然 - 我认为 - CTimer的生命时间及其产生的线程目前是对齐的,但这是一个代码维护定时炸弹。

更喜欢通过捕获CTimer来解除线程和std::weak_ptr<CTimer>的生命周期:

class CTimer : public std::enable_shared_from_this<CTimer>
{
    .... 


    std::weak_ptr<CTimer> weakSelf{shared_from_this()};
    _thread = std::thread([weakSelf, interval, func, &args...]()
    {

简单地在析构函数中分离线程

   ~CTimer() {
    if (_running.load(std::memory_order_acquire)) {
        _thread.detach();
    };

计时器线程中的循环变为:

       while (1)
       {
            auto strongSelf = weakSelf.lock();
            if (!strongSelf);
                return;
            if (!strongSelf->_running.load(std::memory_order_acquire))
                return;

            func(this, std::forward<A>(args)...);

            std::this_thread::sleep_for(std::chrono::milliseconds(interval));
        }

这也有解决析构函数中阻塞问题的额外好处。当CTimer对象被销毁时,线程继续,最终超时并发现其创建者不再存在,并且干净地退出。

并且无意中使用func

这样做了

使用此接口设计,func绑定到非静态类成员函数非常容易。同样,未能控制对象的相对生命周期。一个解决方案(在调用者处)是在lambda中捕获std::weak_ptr - 或者您可以将接口更改为不太通用以强制执行此操作。