如何在BOOST中向一个线程发送信号并在另一个线程中执行相应的槽函数?

12

例如,在Qt中,如果您在GUI线程之外的线程中发出信号,则该信号将被排队并稍后在GUI线程中执行,那么是否有一种使用boost的方式来实现这一点?

谢谢

5个回答

19

使用boost::asio::io_service作为事件循环。您可以在此对象内发布任务,并在另一个线程中以线程安全的方式执行它们:

struct MyClass
{
    boost::io_service service;
    void doSomethingOp() const { ... }

    void doSomething()
    {
        service.post(boost::bind(&MyClass::doSomethingOp, this));
    }

    void loop()
    {
            service.run(); // processes the tasks
    }
};

boost::signal<void()> mySignal;

MyClass myClass;
mySignal.connect(boost::bind(&MyClass::doSomething, boost::ref(myClass)));

// launches a thread and executes myClass.loop() there
boost::thread t(boost::bind(&MyClass::loop(), boost::ref(myClass)));

// calls myClass.doSomething() in this thread, but loop() executes it in the other
mySignal(); 

3
谢谢这个非常有帮助的样例!由于boost::signal已经废弃,我需要使用boost::signals2::signal<>。 - synapse

5
以下是翻译的结果:

下面是关于io_serviceexecutor_work_guardsignals2::signal的完整示例。

  • io_service是事件循环处理程序
  • executor_work_guard确保m_service.run()不仅执行一次
  • signal/slot将发送者和接收者解耦
  • thread运行io_service的所有进程
#include <boost/thread.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/executor_work_guard.hpp>
#include <boost/signals2/signal.hpp>

class IOService
{
public:
  IOService() : m_worker(boost::asio::make_work_guard(m_service)) {}
  ~IOService() {}

  // slot to receive signal
  void slotMessage(std::string msg)
  {
    m_service.post(boost::bind(&IOService::process, this, msg));
  }

  // start/close background thread
  bool start()
  {
    if (m_started)
      return true;
    m_started = true;

    // start reader thread
    m_thread = boost::thread(boost::bind(&IOService::loop, this));
    return m_started;
  }

  void loop()
  {
    m_service.run();
  }

  void close()
  {
    m_worker.reset();
    if (m_thread.joinable())
      m_thread.join();
    m_started = false;
  }

  // process
  void process(std::string msg)
  {
    printf("process %s\n", msg.c_str());
  }

private:
  bool m_started = false;
  boost::asio::io_service m_service;
  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_worker;
  boost::thread m_thread;
};

int main()
{
  // service instance
  IOService serv;
  serv.start();

  // signal to slot
  boost::signals2::signal<void(std::string)> signalMessage;
  signalMessage.connect(boost::bind(&IOService::slotMessage, boost::ref(serv), _1));

  // send one signal
  signalMessage("abc");

  // wait and quit
  boost::this_thread::sleep(boost::chrono::seconds(2));
  serv.close();
}

3

由于boost没有提供事件循环,因此不能直接处理信号。

要在另一个线程中处理信号,该线程需要检查应运行的处理程序队列并执行它们(通常意味着某种事件循环)。Boost没有提供这样的功能,因此您需要从其他地方获取或编写它。

如果您有一个不提供信号的事件循环(或使用队列实现一些简单的解决方案),则可以通过覆盖 operator + = 来滥用boost.signals2(而不是boost.signals,因为该版本不是线程安全的)将每个处理程序包装在某些东西中,以便在其他线程中排队执行。您甚至可以实现具有返回值的信号(这在Qt中不受支持,但在boost中受支持),但必须小心避免死锁。


2
Chila的回答是正确的,但缺少一个重要的内容:一个boost::thread对象只会调用传递给它的函数一次。由于boost::io_service没有工作可做,直到发出信号,线程将立即结束。为了解决这个问题,有一个boost::asio::io_service::work类。在调用io_servicerun()方法之前,应该创建一个工作对象并将其传递给io_service
//as a class variable
std::shared_ptr<boost::asio::io_service::work> worker;

//before you call run() of the io_service yourIOService
worker = std::make_shared<boost::asio::io_service::work>(yourIOService);

//If you want the service to stop
worker.reset();

注意:在编写此文时(boost 1.67),该方法已被弃用,您应该使用io_context::executor_work_guard(基本上与io_service::work具有相同的功能)。然而,当我使用新方法时无法编译,而work解决方案仍然适用于boost 1.67。

1

由于某些原因,boost::asio::executor_work_guard<boost::asio::io_context::executor_type> 的赋值运算符被删除了,但您仍然可以构造它。

这是我发布一些可移动的 Event 对象并在运行 io_context::run() 的线程上处理它的代码版本:

class MyClass {
public:
  MyClass () : m_work(boost::asio::make_work_guard(m_service)) {}

  size_t loop() {
    return m_service.run();
  }

  void stop() {
    m_work.reset();
  }

  void doSomething(Event&& e) {
    m_service.post([this, e=std::move(e)]{ doSomethingOp(std::move(e)); });
  }

private:
  void doSomethingOp(const Event& event) {
    ...
  }
//data:
  boost::asio::io_context m_service;
  boost::asio::executor_work_guard<boost::asio::io_context::executor_type> m_work;
};

需要使用C++14,并已在VS2017和GCC 6.4上进行了测试,包括线程和内存检查。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接