2017-01-10 15 views
-1

我想建立一个帮助类,它可以接受通过std :: bind创建的std :: function),这样我就可以调用这个类从另一个线程:std ::函数结合线程C++ 11失败向量中的调试断言

短的例子:

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

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

然而,呼吁停止()时,我目前的实施,将提高以下错误: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"; 

} 
+0

但是在引用代码中没有矢量,你是否忽略了重要的或者你说你不使用向量explcitily? –

+0

我以前没有使用Google Test,但是我认为你的TEST()定义应该包含某种ASSERT()声明? – Mitch

+1

offtopic:'std :: thread'是可移动的,你不需要(并且想要)通过指针存储它并手动删除它 –

回答

4

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

部分由于此原因,您的stop()不会停止任何操作。即使没有这个UB(也就是说,你用一个原子“修复”它),它只是试图说“哦,停在某个点”,最后它甚至不会保证停止发生。

您的代码不必要地呼叫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::futures得到信息。


学习如何编程有许多障碍。如果你已经得到这么多,你已经通过了一些。但是你可能认识那些在你身边学习的人,他们在早期的一个障碍中倒下了。

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

  • 流量控制。

  • 子过程和功能。

  • 循环。

  • 递归。

  • 指针/引用和动态与自动分配。

  • 动态生命周期管理。

  • 对象和动态分派。

  • 复杂

  • 坐标空间

  • 消息

  • 线程和并发

  • 非均匀地址空间,序列化和网络

  • 函数编程,荟萃功能,柯里化,部分应用,Monads

此列表不完整。

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

螺纹加工困难。做它简单的方法。动态寿命管理很难。做它简单的方法。在这两种情况下,非常聪明的人都已经掌握了“手动”的方式来做到这一点,结果是程序呈现随机不可预知/未定义的行为,并导致很多崩溃。通过手动资源分配和取消分配和多线程代码可以工作,但结果通常是小程序意外工作的人(他们在你修复了你注意到的错误的情况下工作)。当你掌握它时,最初掌握的方式是把整个程序的“状态”放在头上,并理解它的工作原理;这无法扩展到很多开发人员的代码库,所以通常你会遇到大型程序意外工作。

make_unique风格和基于唯一不可变共享数据的线程都是可组合的策略。这意味着如果小块是正确的,并且将它们放在一起,则生成的程序是正确的(关于资源寿命和并发性)。这使得本地小规模线程或资源管理的掌握能够适用于这些策略工作领域中的大规模程序。

+0

会给这50个upvotes,如果我可以 –

+0

哇,谢谢你反馈意见,我会努力工作,并将我的结果发布给我未来的问题。 读下一行可能会让你觉得我总是错过了这一点,你会被警告:) 我知道这个班级有一些问题,我知道使用不同步的is_running_可能会造成很大的伤害。 但是在我的期望中(作为一个C++初学者),尽管500毫秒的睡眠时间和停止后2秒的睡眠时间应该足以确保最后一次访问函数在主线程超出了范围。 但是,修复代码的时间:) –

+0

@ mac.1如果您读取的数据没有同步,则这是未定义的行为。在这种情况下,在释放中,读线程缓存读取的结果。它知道没有与读取同步,所以它可以读取一次,**再也不看它了,因为没有定义的方式来修改它。它不可能在这个线程中发生,并且在没有同步的情况下,允许忽略它在其他线程中发生的可能性。 UB意味着编译器可以自由地假定它不会发生,并且优化就好像它没有发生一样。 – Yakk

0

后从@Yakk指导下,我决定调整自己的PROGRAMM:

  1. bool is_running_将变更为td::atomic<bool> is_running_
  2. stop()不仅会触发停止,但会activly等待线程通过停止的new一个thread_->join()
  3. 所有呼叫都与std::make_unique<std::thread>(&LoopThread::executeLoop, this)
  4. 代替我与复制或移动构造函数没有经验。所以我决定禁止他们。这应该防止我意外地使用这个。如果我在未来某个时候将需要那些我必须承担thoose
  5. thread_->detach()一个deepter看被替换thread_->join()(见2)

这是列表的末尾。

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(); 
}