【问题标题】:Change std::thread execution flow with signals in c++用 C++ 中的信号改变 std::thread 执行流程
【发布时间】:2013-05-29 17:11:53
【问题描述】:

我有一个程序启动一个 std::thread 执行以下操作:睡眠 X,执行一个函数,终止。

create std::thread(Xms, &func)
  wait Xms
  then do func()
  end

我想知道是否可以例如向我的 std::thread 发送一个信号,以便立即中断睡眠并执行 func,然后退出。

我是否需要将信号发送到 std::thread::id 才能执行此操作?

我的线程以这种方式启动,带有一个 lambda 函数:

template<typename T, typename U>
void                          execAfter(T func, U params, const int ms)
{
  std::thread                 thread([=](){
      std::this_thread::sleep_for(std::chrono::milliseconds(ms));
      func(params);
    });
  thread.detach();
}

【问题讨论】:

  • 出于好奇:你为什么要等待?
  • @stefan 我实际上是在炸弹人游戏中实现炸弹,它们在 x 次后爆炸,我希望在炸弹爆炸之间实现“连锁反应”。如果炸弹爆炸与另一颗炸弹相撞,她必须立即爆炸,而不是等待 Xms 爆炸。
  • 有可能,但很难。 'C++ Concurrency in Action'(第 9.2 章)用了整整 10 页来勾画一个实现并讨论它的缺陷(我不打算在这里重复)。如果 Boost 是一个选项,请考虑使用 boost::thread 来代替 interruptible threads
  • @Mayerz 我明白了。对我来说,每颗炸弹都有自己的线程似乎不合适。我建议使用并发(或至少是线程安全的)BombHandler,它可以让炸弹在超时或接近另一次爆炸时爆炸。线程创建相当昂贵,所以我会避免每次炸弹都这样做。
  • @ComicSansMS 可悲的是不能使用 Boost,学校项目。 :(

标签: c++ multithreading c++11 signals


【解决方案1】:

如果线程模型无法更改,则使用std::condition_variablewait_for 将是可行的方法。在下面的代码 sn-p 中,condition_variable 的使用被包装到一个类中,该类的对象必须跨线程共享。

#include <iostream>
#include <atomic>
#include <condition_variable>
#include <thread>
#include <chrono>

class BlockCondition
{
    private:
        mutable std::mutex m;
        std::atomic<bool> done;
        mutable std::condition_variable cv;
    public:
        BlockCondition()
        :
           m(),
           done(false),
           cv()
        {
        }
        void wait_for(int duration_ms)
        {
            std::unique_lock<std::mutex> l(m);
            int ms_waited(0);
            while ( !done.load() && ms_waited < duration_ms )
            {
               auto t_0(std::chrono::high_resolution_clock::now());
               cv.wait_for(l, std::chrono::milliseconds(duration_ms - ms_waited));
               auto t_1(std::chrono::high_resolution_clock::now());
               ms_waited += std::chrono::duration_cast<std::chrono::milliseconds>(t_1 - t_0).count();
            }
        }
        void release()
        {
            std::lock_guard<std::mutex> l(m);
            done.store(true);
            cv.notify_one();
        }
};

void delayed_func(BlockCondition* block)
{
    block->wait_for(1000);
    std::cout << "Hello actual work\n";
}

void abortSleepyFunction(BlockCondition* block)
{
    block->release();
}

void test_aborted()
{
    BlockCondition b();
    std::thread delayed_thread(delayed_func, &b);
    abortSleepyFunction(&b);
    delayed_thread.join();
}

void test_unaborted()
{
    BlockCondition b();
    std::thread delayed_thread(delayed_func, &b);
    delayed_thread.join();
}

int main()
{
    test_aborted();
    test_unaborted();
}

请注意,可能存在虚假唤醒,过早中止等待调用。考虑到这一点,我们计算实际等待的毫秒数并继续等待,直到设置完成标志。

【讨论】:

  • 这看起来很棒!想我会用这个。更改奖励的所有者公平吗?
  • 接受答案是个人选择,您可以随时更改。为避免有人感到生气,您不应该很快这样做。花点时间决定什么对您的帮助最大。
  • 没错,我会花时间消化你的,但看起来很有趣。我用完就打绿勾好吗?
  • 如果在没有先调用 wait() 的情况下销毁 BlockCondition,您的 m2 互斥锁会给您带来麻烦。此外,您在所有确实需要它们的架构上获得虚假唤醒(例如,每个依赖于 ll/sc 的架构)。
  • @ComicSansMS 感谢您指出这一点,我已经相应地调整了答案。
【解决方案2】:

正如 cmets 中所指出的,这首先不是解决问题的最明智的方法。由于实施适当的中断机制非常复杂且极易出错,因此以下是解决方法的建议:

不要在整个超时时间内休眠,而是简单地循环一个固定的小尺寸(例如 10 毫秒)的休眠,直到所需的持续时间过去。每次睡眠后,您检查一个原子标志是否请求中断。这是一个肮脏的解决方案,但却是最快的。

或者,为每个线程提供condition_variable 并在其上执行wait 而不是执行this_thread::sleep。通知条件变量指示中断请求。您可能仍然需要一个额外的标志来防止虚假唤醒,这样您就不会意外地过早返回。

【讨论】:

  • 感谢朋友们的回答,我必须和我的工作人员讨论这个问题。看起来我们陷入了困境 :) 我可能会重新考虑我的炸弹系统。
  • @Mayerz 这是一个肮脏,肮脏的黑客。最好希望没有人仔细查看代码!
【解决方案3】:

好的,为了解决这个问题,我找到了一个新的实现,它受到您所有答案的启发,非常感谢。

首先我要做一个 BombHandler 项目,在主游戏项目中。它将有一个包含所有 Bomb 项目的属性。

这个 BombHandler 将是一个单例,包含一个将在线程中执行的 timerLoop() 函数(这样我只使用一个线程来处理 xxx 炸弹,这样更有效)

timerLoop() 将休眠(50) 然后通过整个 std::list 元素并调用 Bomb::incrTimer() 将其内部 _timer 属性无限增加 10ms,并检查必须爆炸的炸弹。

例如,当它们达到 2000 毫秒时,将调用 BombHandler.explode(),引爆炸弹并删除它。

如果另一个炸弹在范围内,Bomb::touchByFire() 将被调用,并将 Bomb 的内部属性 _timer 设置为 TIME_TO_EXPLODE (1950ms)。

然后它会在 50ms 后被 BombHandler::explode() 爆炸。

这不是一个很好的解决方案吗?

再次感谢您的回答!希望这能有所帮助。

【讨论】:

  • 听起来是一个合理的解决方案。很高兴你明白了:)
  • timerLoop() 将使用睡眠(50) ... 并且 ... 将其内部 _timer 属性增加 10 毫秒 预计炸弹会过早爆炸 :)
  • @InnocentBystander 你这个死灵法师! Usleep(50) 是 50 毫秒吗?
  • @Mayerz,usleep(50) 是 50 微秒,相当于 0.05 毫秒... KABOOM
  • 爱因斯坦相对论的实际例子:)
猜你喜欢
  • 1970-01-01
  • 2015-09-28
  • 2020-02-13
  • 2023-03-08
  • 2014-03-04
  • 2021-12-10
  • 2016-10-20
  • 1970-01-01
相关资源
最近更新 更多