什么是同步此事件实现的最佳方式

时间:2012-01-11 23:53:42

标签: c++ multithreading events delegates

这是我尝试实现C ++事件。

class Event{
    typedef std::tr1::function<void( int& )> CallbackFunction;
    std::list< CallbackFunction > m_handlers;

    template<class M>
    void AddHandler(M& thisPtr, void typename (M::*callback)(int&)) 
    {           
        CallbackFunction bound = std::tr1::bind(callback, &thisPtr, _1);
        m_handlers.push_back(bound);
    }

    void operator()(int& eventArg) 
    { 
        iterate over list...
        (*iter)(eventArg);

    }}

这里的麻烦是线程安全。如果同时调用AddHandleroperator(),事情可能会中断。

同步此功能的最佳方法是什么?使用互斥锁可能会降低性能。我想知道在这种情况下boost :: signals或C#事件的幕后会发生什么。

3 个答案:

答案 0 :(得分:2)

首先,在您将任何实施可能性视为“不够快”之前,您需要确定实际的性能要求。您是否会每秒触发这些事件数千次?如果你是,那么你是否真的需要一直在处理程序容器中添加处理程序。

如果这两个问题的答案出于某种原因实际上是“是”,那么您可能需要调查无锁容器。这意味着构建自己的容器而不是能够使用stl列表。无锁容器将使用原子内在函数(例如,在Windows中的InterlockedCompareExchange)以原子方式确定列表的末尾是否为NULL或其他。然后,他们将使用类似的内在实际附加到列表中。如果多个线程同时尝试添加处理程序,则会发生其他复杂情况。

然而,在多核机器和指令重新订购等等的世界中,这些方法可能充满危险。我个人使用的事件系统与你描述的不同,我将它用于关键部分(至少在Windows中非常有效),而且我没有遇到性能问题。但另一方面,通过事件系统发送的任何信息都不会超过20Hz左右。

与任何与绩效相关的问题一样,答案总是基于另一个问题的答案;你究竟在哪里需要你的表现?

答案 1 :(得分:1)

互斥锁绝对是您正在寻找的。如果每个事件都有自己的互斥锁,我不会太担心性能;原因是,除非您在处理事件期间添加了大量处理程序,否则互斥锁不会出现争用并使您放慢速度。

但是,如果有多个线程在同一对象上调用operator()方法,则此互斥锁可能成为问题。但是没有它,你怎么能确保以线程安全的方式调用你的回调呢? (我注意到你传入一个整数引用并返回void,所以我假设这些不是可重入的处理程序。)

编辑:你的评论非常好的问题。说实话,我从未考虑过互斥是否以同步方式使用时有很多开销。所以我把这个小测试放在一起。


#include <stdio.h>
#include <pthread.h>

#define USE_PTHREAD_MUTEX 1

int main(int argc, char * argv[]) {

pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL);

long useless_number = 0;
long counter;

  for(counter = 0; counter < 100000000; counter++) {
    #if USE_PTHREAD_MUTEX
    pthread_mutex_lock(&mutex);
    #endif
    useless_number += rand();

    #if USE_PTHREAD_MUTEX
    pthread_mutex_unlock(&mutex);
    #endif
  }

  printf("%ld\n", useless_number);

}

我在我的系统上运行了这个并获得了以下运行时。

使用USE_PTHREAD_MUTEX 0时,平均运行时间为1.2秒。

使用USE_PTHREAD_MUTEX 1时,平均运行时间为2.8秒。

因此,要回答你的问题,肯定有开销。你的旅费可能会改变。此外,如果多个线程竞争访问资源,则必须花费更多时间来阻止。此外,在纯粹的同步上下文中,与等待互斥锁进行锁定/解锁相比,访问共享资源的时间可能更长。也就是说,与这些事情相比,互斥逻辑本身的开销可能微不足道。

答案 2 :(得分:1)

如果列表确实是您的类,那么由于它的性质,您不需要在每次访问它时锁定。您将锁定互斥锁以发布到列表的末尾,并且当您认为可能已到达结束时也会锁定。

您应该保留班级中处理程序数量的计数,当您即将开始迭代时,您可以愉快地迭代而不会锁定,直到您达到此数字。

如果要删除处理程序,那么您会遇到更多的线程争用问题。