Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

how in BOOST send a signal in a thread and have the corresponding slot executed in another thread?

Tags:

c++

signals

boost

In Qt for instance if you emit a signal in a thread other that the GUI thread, the signal is enqueued and executed later in the GUI thread, is there a way to do that with boost?

thanks

like image 918
Guillaume Paris Avatar asked Feb 19 '11 11:02

Guillaume Paris


4 Answers

For an event loop use boost::asio::io_service. You can post tasks inside this object and have another thread execute them, in a thread safe way:

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(); 
like image 146
chila Avatar answered Nov 18 '22 20:11

chila


Here's a complete example of the above mentioned io_service, executor_work_guard, signals2::signal.

  • io_service is the event loop handler
  • executor_work_guard make sure the m_service.run() doesn't only execute once
  • signal/slot decouples the sender and receiver
  • the thread runs all the process of the 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();
}
like image 4
Neil Z. Shao Avatar answered Nov 18 '22 20:11

Neil Z. Shao


Not directly, because boost does not provide an event loop.

To have a signal handled in another thread, that another thread needs to be checking the queue of handlers it should run and execute them (which usually means some kind of event-loop). Boost does not provide one, so you'll need to get it from elsewhere or write it.

If you have an event-loop, that does not provide signals, (or implement some simple solution with queues) you should be able to (ab)use boost.signals2 (not boost.signals, because that version is not thread-safe) by overriding the operator+= to wrap each handler in something, that will queue it for execution in the other thread. You might even be able to implement it for signals with return values (which is not supported by Qt, but is supported by boost), but you'll have to be careful to avoid dead-lock.

like image 3
Jan Hudec Avatar answered Nov 18 '22 21:11

Jan Hudec


Chila's answer is correct, but it's missing one important thing: A boost::thread object will only call the function its passed once. Since the boost::io_service has no work to do until the signal is emitted, the thread will finish immediately. To counter this there is a boost::asio::io_service::work class. Before you call the run() method of the io_service you should create a work object and pass it the 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();

Note: At the time of writing (boost 1.67) this method is already deprecated and you are supposed to use io_context::executor_work_guard (basically same functionality as io_service::work). I was not able to compile when using the new method though, and the work solution is still working in boost 1.67.

like image 2
Kamairo Avatar answered Nov 18 '22 20:11

Kamairo