std :: function与线程c ++ 11结合使用会在向量中失败调试断言

时间:2017-01-10 01:40:35

标签: c++ multithreading c++11 vector visual-studio-2015

我想构建一个可以接受通过std :: bind创建的std :: function的辅助类,这样我就可以从另一个线程中调用这个类:

简短的例子:

void loopme() {
    std::cout << "yay";
}

main () {
   LoopThread loop = { std::bind(&loopme) };
   loop.start();
   //wait 1 second
   loop.stop();
   //be happy about output
}

但是,当调用stop()时,我当前的实现会引发以下错误:debug assertion Failed , see Image: i.stack.imgur.com/aR9hP.png

有谁知道为什么会抛出错误? 在这个例子中我甚至不使用向量。 当我不从线程中调用loopme但直接输出到std :: cout时,不会抛出任何错误。

这是我班级的全部实施:

class LoopThread {
public:
    LoopThread(std::function<void(LoopThread*, uint32_t)> function) : function_{ function }, thread_{ nullptr }, is_running_{ false }, counter_{ 0 } {};
    ~LoopThread();
    void start();
    void stop();
    bool isRunning() { return is_running_; };
private:
    std::function<void(LoopThread*, uint32_t)> function_;
    std::thread* thread_;
    bool is_running_;
    uint32_t counter_;
    void executeLoop();
};

LoopThread::~LoopThread() {
    if (isRunning()) {
        stop();
    }
}

void LoopThread::start() {
    if (is_running_) {
        throw std::runtime_error("Thread is already running");
    }
    if (thread_ != nullptr) {
        throw std::runtime_error("Thread is not stopped yet");
    }
    is_running_ = true;
    thread_ = new std::thread{ &LoopThread::executeLoop, this };
}

void LoopThread::stop() {
    if (!is_running_) {
        throw std::runtime_error("Thread is already stopped");
    }
    is_running_ = false;
    thread_->detach();
}

void LoopThread::executeLoop() {
    while (is_running_) {
        function_(this, counter_);
        ++counter_;
    }
    if (!is_running_) {
        std::cout << "end";
    }
    //delete thread_;
    //thread_ = nullptr;
}

我使用以下Googletest代码进行测试(但是包含代码的简单主方法应该有效):

void testfunction(pft::LoopThread*, uint32_t i) {
    std::cout << i << ' ';
}

TEST(pfFiles, TestLoop)
{
    pft::LoopThread loop{ std::bind(&testfunction, std::placeholders::_1, std::placeholders::_2) };
    loop.start();

    std::this_thread::sleep_for(std::chrono::milliseconds(500));

    loop.stop();
    std::this_thread::sleep_for(std::chrono::milliseconds(2500));
    std::cout << "Why does this fail";

}

2 个答案:

答案 0 :(得分:4)

您对is_running_的使用是未定义的行为,因为您在一个线程中写入并在没有同步障碍的情况下读取另一个线程。

部分原因是,stop()并未阻止任何事情。即使没有这个UB(也就是说,你通过使用原子来修复&#34;它),它只是试着说'oy,在某个时刻停止&#34;,到最后它甚至没有尝试保证停止发生。

您的代码不必要地调用new。没有理由在这里使用std::thread*

您的代码违反了5的规则。您编写了一个析构函数,然后忽略了复制/移动操作。这太荒谬了。

由于stop()没有任何后果来阻止一个线程,带有this指针的线程比你的LoopThread对象更长。 LoopThread超出范围,破坏了std::thread存储的指针。仍在运行的executeLoop调用已被销毁的std::function,然后将计数器递增到无效的内存(可能在已创建另一个变量的堆栈上)。

粗略地说,在代码的每3-5行中使用std线程有一个基本错误(不包括接口声明)。

除了技术错误之外,设计也是错误的;使用detach几乎总是一个可怕的想法;除非你有一个承诺,你在线程退出时做好准备,然后在某个地方等待完成那个承诺,这样做并获得类似干净和可靠的程序关闭的任何事情几乎是不可能的。

作为一种猜测,向量错误是因为你正在遍历堆栈内存并且遵循几乎无效的指针来查找要执行的函数。测试系统要么将一个数组索引放在你正在废弃的地方,然后调试vector捕获它超出界限,或者一个函数指针对你的std函数执行运行有一半意义,或者某些东西

仅通过线程之间的同步数据进行通信。这意味着atomic数据或mutex被保护,除非你变得荒谬可笑。你不了解线程就足以获得幻想。你不会理解线程足以复制那些喜欢并正确使用它的人。不要花哨。

不要使用new。几乎从不使用new。如果您必须,请使用make_sharedmake_unique。但很少使用它们。

不要detach一个帖子。期。是的,这意味着您可能必须等待它完成一个循环或某些。处理它,或写一个线程管理器,在关机或等等时等待。

非常清楚什么线程拥有哪些数据。非常清楚线程何时完成数据。避免使用线程之间共享的数据;通过传递值(或指向不可变共享数据的指针)进行通信,并从std::future获取信息。

学习如何编程有很多障碍。如果你已经走到这一步,你已经通过了一些。但是你可能知道那些在你身边学到的人在早期的一个障碍中摔倒了。

  • 顺序,事情一个接一个地发生。

  • 流量控制。

  • 子程序和功能。

  • 循环。

  • 递归。

  • 指针/参考和动态与自动分配。

  • 动态生命周期管理。

  • 对象和动态调度。

  • 复杂性

  • 坐标空间

  • 消息

  • 线程和并发

  • 非统一地址空间,序列化和网络

  • 功能编程,元函数,currying,部分应用,Monads

此列表不完整。

关键是,这些障碍中的每一个都会导致您作为程序员崩溃并失败,并且正确地解决这些障碍是很困难的。

线程很难。这样做很简单。动态生命周期管理很难。这样做很简单。在这两种情况下,非常聪明的人都掌握了&#34;手册&#34;这样做的方法,结果是程序显示随机不可预测/未定义的行为并且崩溃很多。通过手动资源分配和解除分配以及多线程代码的混乱可以使用,但结果通常是小程序意外工作的人(他们在您修复了您注意到的错误的情况下工作)。当你掌握它时,初始掌握的形式是持有整个程序的状态&#34;在你的头脑中,了解它是如何工作的;这无法扩展到大量的开发人员代码库,所以你通常会毕业于拥有意外工作的大型程序。

基于make_unique样式和仅基于不可变共享数据的线程都是可组合策略。这意味着如果小块是正确的,并将它们放在一起,则生成的程序是正确的(关于资源生命周期和并发性)。这允许当地掌握小规模线程或资源管理,以适用于这些策略有效的领域中的大规模计划。

答案 1 :(得分:0)

按照@Yakk的指南后,我决定重组我的程序:

  1. bool is_running_将更改为td::atomic<bool> is_running_
  2. stop()不仅会触发停止,还会通过thread_->join()
  3. 激活等待线程停止
  4. new的所有来电均被std::make_unique<std::thread>( &LoopThread::executeLoop, this )
  5. 取代
  6. 我没有复制或移动构造函数的经验。所以我决定禁止他们。这应该可以防止我意外地使用它。如果我将来某个时候需要那些我必须更深入地看待那些
  7. thread_->detach()已替换为thread_->join()(请参阅2.)
  8. 这是清单的结尾。

    class LoopThread {
    public:
        LoopThread(std::function<void(LoopThread*, uint32_t)> function) : function_{ function }, is_running_{ false }, counter_{ 0 } {};
        LoopThread(LoopThread &&) = delete;
        LoopThread(const LoopThread &) = delete;
        LoopThread& operator=(const LoopThread&) = delete;
        LoopThread& operator=(LoopThread&&) = delete;
        ~LoopThread();
        void start();
        void stop();
        bool isRunning() const { return is_running_; };
    private:
        std::function<void(LoopThread*, uint32_t)> function_;
        std::unique_ptr<std::thread> thread_;
        std::atomic<bool> is_running_;
        uint32_t counter_;
        void executeLoop();
    };
    
        LoopThread::~LoopThread() {
        if (isRunning()) {
            stop();
        }
    }
    
    void LoopThread::start() {
        if (is_running_) {
            throw std::runtime_error("Thread is already running");
        }
        if (thread_ != nullptr) {
            throw std::runtime_error("Thread is not stopped yet");
        }
        is_running_ = true;
        thread_ = std::make_unique<std::thread>( &LoopThread::executeLoop, this );
    }
    
    void LoopThread::stop() {
        if (!is_running_) {
            throw std::runtime_error("Thread is already stopped");
        }
        is_running_ = false;
        thread_->join();
        thread_ = nullptr;
    }
    
    void LoopThread::executeLoop() {
        while (is_running_) {
            function_(this, counter_);
            ++counter_;
        }
    }
    
    TEST(pfThread, TestLoop)
    {
        pft::LoopThread loop{ std::bind(&testFunction, std::placeholders::_1, std::placeholders::_2) };
        loop.start();
        std::this_thread::sleep_for(std::chrono::milliseconds(50));
        loop.stop();
    }