多线程事件系统

时间:2013-12-19 18:54:35

标签: c++ multithreading event-handling

我正在尝试用C ++设计一个多线程事件系统。在其中,对象可以位于不同的线程中,并且每个对象应该能够为其他线程排队事件。每个线程都有自己的事件队列和事件调度程序,以及事件循环。应该可以更改对象的线程亲和力。

假设我们有两个线程:A和B,以及一个属于B的对象myobj。显然,A需要一个指向myobj的指针,以便能够向它发送事件。 A没有任何指向B的指针,但它需要一些方法来获取对它的引用,以便能够锁定事件队列并将事件添加到它。

我可以在myobj中存储指向B的指针,但是我显然需要保护myobj。如果我在myobj中放置互斥锁,则在互斥锁被锁定时可能会破坏myobj,从而导致分段错误。

我还可以使用全局表,将每个对象与其对应的线程相关联。但是,这将占用大量内存并导致任何想要发送事件的线程在A完成之前阻塞 编

实施此措施的最有效安全策略是什么?可能有某种设计模式吗?

提前致谢。

4 个答案:

答案 0 :(得分:0)

我自己没有使用它,但Boost.Signals2声称是线程安全的。

  

Boost.Signals2的主要动机是提供原始Boost.Signals库的一个版本,可以在多线程环境中安全使用。

当然,使用它会使你的项目依赖于提升,这可能不符合你的利益。

[edit]似乎插槽是在发射线程中执行的(没有队列),所以这可能不是你想到的那样。

答案 1 :(得分:0)

我会考虑让线程成为类的一部分来封装它们。这样,您可以轻松地围绕线程循环设计接口(作为这些类的成员函数提供),并定义了将数据发送到线程循环的入口点(例如,使用受互斥锁保护的std :: queue)。

我不知道这是否是一个众所周知的指定设计模式,但这就是我在工作中使用的全天生产代码,而我(以及我的同事)感觉和体验相当不错。

我会试着给你一个观点:

class A {

public:
    A() {}

    bool start();
    bool stop();
    bool terminate() const;
    void terminate(bool value);

    int data() const;
    void data(int value);
private:
    std::thread thread_;

    void threadLoop();

    bool terminate_;
    mutable std::mutex internalDataGuard_;
    int data_;
};

bool A::start() {
    thread_ = std::thread(std::bind(this,threadLoop));
    return true;
}

bool A::stop() {
    terminate(true);
    thread_.join();
    return true;
}

bool A::terminate() const {
    std::lock_guard<std::mutex> lock(internalDataGuard_);
    return terminate_;
}

void A::terminate(bool value) {
    std::lock_guard<std::mutex> lock(internalDataGuard_);
    terminate_ = value;
}

int A::data() const {
    std::lock_guard<std::mutex> lock(internalDataGuard_);
    return data_;
}

void A::data(int value) {
    std::lock_guard<std::mutex> lock(internalDataGuard_);
    data_ = value;
    // Notify thread loop about data changes
}

void A::threadLoop() {
     while(!terminate())
     {
         // Wait (blocking) for data changes
     }
}

要设置数据更改的信令,有几种选择和(OS)约束:

用于唤醒线程循环以处理已更改/新数据的最简单方法是信号量。在中,信号量的最近约是条件变量。 pthreads API的高级版本还提供条件变量支持。无论如何,因为只有一个线程应该在那里等待,并且不需要任何类型的事件扩展,所以应该使用简单的锁定机制来实现它。

如果您可以选择使用高级操作系统,则可能更喜欢使用s.th实现事件信令。比如poll(),它在用户空间提供无锁实现。

诸如boost,Qt,Platinum C ++等其他框架也支持信号/插槽抽象的事件处理,您可以查看他们的文档和实现,以掌握必要/最先进的技术。

答案 2 :(得分:0)

  

显然,A需要一个指向myobj的指针才能发送   它的事件。

我质疑上面的假设 - 对我来说,允许线程A有一个指向由线程B控制/拥有/访问的对象的指针有点麻烦...特别是,一些代码在线程中运行以后可能会试图使用该指针直接调用myobj上的方法,导致竞争条件和不和谐;或者B可能会删除myobj,此时A持有一个悬空指针,因此处于不稳定的状态。

如果我正在设计系统,我会尝试以这样的方式完成交叉线程消息传递,而不需要指向其他线程中的对象,这是你提到的原因 - 它们是不安全,特别是这样的指针可能随时成为悬空指针。

那么问题就变成了,如果我没有指向该对象的指针,如何向另一个线程中的对象发送消息?

一种方法是为每个对象提供一个唯一的ID,通过它可以指定它。此ID可以是整数(使用原子计数器或类似的硬编码或动态分配),或者如果您希望它更易于人类阅读,则可能是短字符串。

然后代替线程A中的代码直接将消息发送到myobj,它将向线程B发送消息,并且消息将包括一个字段,指示要接收的对象的ID。消息。

当线程B的事件循环收到消息时,它将使用包含的ID值来查找适当的对象(使用有效的键值查找机制,如std::unordered_map)并调用相应的该对象的方法。如果对象已经被销毁,那么键值查找将失败(因为你有一个机制来确保该对象从其线程的对象映射中删除自己作为其析构函数的一部分) ,因此尝试向被破坏的对象发送消息将干净地失败(而不是调用未定义的行为)。

请注意,此方法确实意味着线程A的代码必须知道哪个线程myobj所拥有,以便知道将消息发送到哪个线程。通常情况下,线程A需要知道这一点,但是如果您正在寻找甚至是关于给定对象在哪个线程中运行的知识的抽象设计,那么您可以包含所有者线程ID作为对象ID,以便postMessage()方法可以检查目标对象ID,以确定将消息发送到哪个线程。

答案 3 :(得分:0)

我已经实现了一个线程包装器基类ThreadEventComponent,用于在自身实例之间发送和处理事件。每个ThreadEventComponent都有自己的事件队列,无论何时使用,都会自动锁定。事件本身由类型为map<EventKey, vector<ThreadEventComponent*>>的静态映射协商,每次使用时也会自动锁定。如您所见,多个ThreadEventComponent派生实例可以订阅同一事件。每个实例都会复制与SendEvent(Event*)一起发送的每个事件,以确保多个线程不会对事件中保存的相同数据进行争夺。

不可否认,这不是最有效的策略,反对共享内存。有关addEvent(Event&)方法的优化。除了缺点之外,它确实适用于配置线程以在主线程之外执行某些操作。

MainLoop()ProcessEvent(Event*)都是由派生类实现的虚函数。只要队列中有可用事件,就会调用ProcessEvent(Event*)。之后,无论事件队列状态如何,都会调用MainLoop()MainLoop()是你应该让你的线程睡觉的地方,以及文件读/写或网络阅读/写作等任何其他操作应该去的地方。

以下代码是我一直致力于让我自己的人使用来解决C ++中的线程问题。此代码从未经过审核,因此我很乐意听取您的任何建议。我知道这个代码示例中有两个不太理想的元素。 1)我在运行时使用new,缺点是找到内存需要时间,但这可以通过创建内存缓冲区来构建ThreadEventComponent基础上的新事件来缓解类。 2)如果在Event中未正确实现,TEvent<T>转换为ProcessEvent会导致运行时错误。我不确定最佳解决方案是什么。

注意:我将EventKey实现为字符串,但您可以将其更改为您希望的任何类型,只要它具有默认值以及可用的相等和赋值运算符。

Event.h

#include <string>

using namespace std;

typedef string EventKey;

class Event
{
public:
  Event()
    : mKey()
  {
  }

  Event(EventKey key)
    : mKey(key)
  {
  }

  Event(const Event& e)
    : mKey(e.mKey)
  {
  }

  virtual ~Event()
  {

  }

  EventKey GetKey()
  {
    return mKey;
  }

protected:
  EventKey mKey;
};

template<class T>
class TEvent : public Event
{
public:
  TEvent()
    : Event()
  {
  }

  TEvent(EventKey type, T& object)
    : Event(type), mObject(object)
  {
  }

  TEvent(const TEvent<T>& e)
    : Event(e.mKey), mObject(e.mObject)
  {
  }

  virtual ~TEvent()
  {
  }

  T& GetObject()
  {
    return mObject;
  }

private:
  T mObject;
};

ThreadEventComponent.h

#include "Event.h"
#include <thread>
#include <atomic>
#include <algorithm>
#include <vector>
#include <queue>
#include <map>
#include <mutex>
#include <assert.h>

class ThreadEventComponent
{
public:
  ThreadEventComponent();
  ~ThreadEventComponent();

  void Start(bool detached = false);
  void Stop();
  void ForceStop();
  void WaitToFinish();
  virtual void Init() = 0;
  virtual void MainLoop() = 0;
  virtual void ProcessEvent(Event* incoming) = 0;

  template<class T>
  void SendEvent(TEvent<T>& e)
  {
    sEventListLocker.lock();

    EventKey key = e.GetKey();
    for (unsigned int i = 0; i < sEventList[key].size(); i++)
    {
      assert(sEventList[key][i] != nullptr);
      sEventList[key][i]->addEvent<T>(e);
    }

    sEventListLocker.unlock();
  }

  void SendEvent(Event& e);
  void Subscribe(EventKey key);
  void Unsubscribe(EventKey key);

protected:
  template<class T>
  void addEvent(TEvent<T>& e)
  {
    mQueueLocker.lock();
    // The event gets copied per thread
    mEventQueue.push(new TEvent<T>(e));
    mQueueLocker.unlock();
  }
  void addEvent(Event& e);

  thread mThread;
  atomic<bool> mShouldExit;

private:
  void threadLoop();

  queue<Event*> mEventQueue;
  mutex mQueueLocker;

  typedef map<EventKey, vector<ThreadEventComponent*>> EventMap;
  static EventMap sEventList;
  static mutex sEventListLocker;
};

ThreadEventComponent.cpp

#include "ThreadEventComponent.h"

ThreadEventComponent::EventMap ThreadEventComponent::sEventList = ThreadEventComponent::EventMap();
std::mutex ThreadEventComponent::sEventListLocker;

ThreadEventComponent::ThreadEventComponent()
{
  mShouldExit = false;
}

ThreadEventComponent::~ThreadEventComponent()
{
}

void ThreadEventComponent::Start(bool detached)
{
  mShouldExit = false;
  mThread = thread(&ThreadEventComponent::threadLoop, this);
  if (detached)
    mThread.detach();
}

void ThreadEventComponent::Stop()
{
  mShouldExit = true;
}

void ThreadEventComponent::ForceStop()
{
  mQueueLocker.lock();
  while (!mEventQueue.empty())
  {
    delete mEventQueue.front();
    mEventQueue.pop();
  }
  mQueueLocker.unlock();
  mShouldExit = true;
}

void ThreadEventComponent::WaitToFinish()
{
  if(mThread.joinable())
    mThread.join();
}

void ThreadEventComponent::SendEvent(Event& e)
{
  sEventListLocker.lock();

  EventKey key = e.GetKey();
  for (unsigned int i = 0; i < sEventList[key].size(); i++)
  {
    assert(sEventList[key][i] != nullptr);
    sEventList[key][i]->addEvent(e);
  }

  sEventListLocker.unlock();
}

void ThreadEventComponent::Subscribe(EventKey key)
{
  sEventListLocker.lock();

  if (find(sEventList[key].begin(), sEventList[key].end(), this) == sEventList[key].end())
  {
    sEventList[key].push_back(this);
  }

  sEventListLocker.unlock();
}

void ThreadEventComponent::Unsubscribe(EventKey key)
{
  sEventListLocker.lock();

  // Finds event listener of correct type
  EventMap::iterator mapIt = sEventList.find(key);
  assert(mapIt != sEventList.end());

  // Finds the pointer to itself
  std::vector<ThreadEventComponent*>::iterator elIt =
    std::find(mapIt->second.begin(), mapIt->second.end(), this);
  assert(elIt != mapIt->second.end());

  // Removes it from the event list
  mapIt->second.erase(elIt);

  sEventListLocker.unlock();
}

void ThreadEventComponent::addEvent(Event& e)
{
  mQueueLocker.lock();
  // The event gets copied per thread
  mEventQueue.push(new Event(e));
  mQueueLocker.unlock();
}

void ThreadEventComponent::threadLoop()
{
  Init();
  bool shouldExit = false;
  while (!shouldExit)
  {
    if (mQueueLocker.try_lock())
    {
      if (mEventQueue.empty())
      {
        mQueueLocker.unlock();
        if(mShouldExit)
          shouldExit = true;
      }
      else
      {
        Event* e = mEventQueue.front();
        mEventQueue.pop();
        mQueueLocker.unlock();
        ProcessEvent(e);
        delete e;
      }
    }

    MainLoop();
  }
}

示例类 - A.h

#include "ThreadEventComponent.h"

class A : public ThreadEventComponent
{
public:
  A() : ThreadEventComponent()
  {
  }

  void Init()
  {
    Subscribe("a stop");
    Subscribe("a");
  }

  void MainLoop()
  {
    this_thread::sleep_for(50ms);
  }

  void ProcessEvent(Event* incoming)
  {
    if (incoming->GetKey() == "a")
    {
      auto e = static_cast<TEvent<vector<int>>*>(incoming);

      mData = e->GetObject();
      for (unsigned int i = 0; i < mData.size(); i++)
      {
        mData[i] = sqrt(mData[i]);
      }

      SendEvent(TEvent<vector<int>>("a done", mData));
    }
    else if(incoming->GetKey() == "a stop")
    {
      StopWhenDone();
    }
  }

private:
  vector<int> mData;
};

示例类 - B.h

#include "ThreadEventComponent.h"

int compare(const void * a, const void * b)
{
  return (*(int*)a - *(int*)b);
}

class B : public ThreadEventComponent
{
public:
  B() : ThreadEventComponent()
  {
  }

  void Init()
  {
    Subscribe("b stop");
    Subscribe("b");
  }

  void MainLoop()
  {
    this_thread::sleep_for(50ms);
  }

  void ProcessEvent(Event* incoming)
  {
    if (incoming->GetKey() == "b")
    {
      auto e = static_cast<TEvent<vector<int>>*>(incoming);

      mData = e->GetObject();
      qsort(&mData[0], mData.size(), sizeof(int), compare);

      SendEvent(TEvent<vector<int>>("b done", mData));
    }
    else if (incoming->GetKey() == "b stop")
    {
      StopWhenDone();
    }
  }

private:
  vector<int> mData;
};

测试示例 - main.cpp

#include <iostream>
#include <random>
#include "A.h"
#include "B.h"

class Master : public ThreadEventComponent
{
public:
  Master() : ThreadEventComponent()
  {
  }

  void Init()
  {
    Subscribe("a done");
    Subscribe("b done");
  }

  void MainLoop()
  {
    this_thread::sleep_for(50ms);
  }

  void ProcessEvent(Event* incoming)
  {
    if (incoming->GetKey() == "a done")
    {
      TEvent<vector<int>>* e = static_cast<TEvent<vector<int>>*>(incoming);
      cout << "A finished" << endl;
      mDataSetA = e->GetObject();

      for (unsigned int i = 0; i < mDataSetA.size(); i++)
      {
        cout << mDataSetA[i] << " ";
      }
      cout << endl << endl;
    }
    else if (incoming->GetKey() == "b done")
    {
      TEvent<vector<int>>* e = static_cast<TEvent<vector<int>>*>(incoming);
      cout << "B finished" << endl;
      mDataSetB = e->GetObject();

      for (unsigned int i = 0; i < mDataSetB.size(); i++)
      {
        cout << mDataSetB[i] << " ";
      }
      cout << endl << endl;
    }
  }

private:
  vector<int> mDataSetA;
  vector<int> mDataSetB;
};

int main()
{
  srand(time(0));

  A a;
  B b;
  a.Start();
  b.Start();

  vector<int> data;
  for (int i = 0; i < 100; i++)
  {
    data.push_back(rand() % 100);
  }

  Master master;
  master.Start();

  master.SendEvent(TEvent<vector<int>>("a", data));
  master.SendEvent(TEvent<vector<int>>("b", data));
  master.SendEvent(TEvent<vector<int>>("a", data));
  master.SendEvent(TEvent<vector<int>>("b", data));
  master.SendEvent(Event("a stop"));
  master.SendEvent(Event("b stop"));

  a.WaitToFinish();
  b.WaitToFinish();

  // cin.get();
  master.StopWhenDone();
  master.WaitToFinish();
  return EXIT_SUCCESS;
}