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

What's the best way to synchronize this event implementation

本文关键字:最佳 方法 是什么 实现 事件 同步      更新时间:2023-10-16

以下是我实现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::信号或C#事件的幕后会发生什么。

首先,在您认为任何实现的可能性不够"快"之前,您需要确定实际的性能要求。你会每秒触发数千次这些事件吗?如果你是,你真的需要一直向处理程序容器添加处理程序吗。

如果出于某种原因,这两个问题的答案实际上都是"是",那么您可能需要调查无锁容器。这意味着要构建自己的容器,而不是使用stl列表。无锁容器将使用原子内部函数(例如windows中的InterlockedCompareExchange)来原子地确定列表的末尾是NULL还是其他。然后,他们将使用类似的内在函数来实际追加到列表中。如果多个线程试图同时添加处理程序,则会出现额外的复杂性。

然而,在一个多核机器和指令重新排序等的世界里,这些方法可能充满危险。我个人使用的事件系统与您描述的没有什么不同,我将其与关键部分一起使用(至少在windows中非常有效),并且我没有遇到性能问题。但另一方面,没有任何东西通过事件系统发送的速度超过大约20Hz

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

互斥锁绝对是您想要的。如果每个Event都有自己的互斥,我就不会太担心性能了;原因是,除非在处理事件的过程中添加了大量处理程序,否则互斥体不太可能发生争用并降低速度。

但是,如果在同一对象上有多个线程调用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("%ldn", useless_number);
}

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

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

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

因此,要回答你的问题,肯定会有开销。您的里程数可能有所不同。此外,如果多个线程正在竞争对一个资源的访问,那么阻塞必然会花费更多的时间。此外,在纯同步上下文中,访问共享资源的时间可能比等待互斥锁锁定/解锁的时间更长。也就是说,互斥逻辑本身的开销与这些东西相比可能微不足道。

如果list真的是你的类,那么由于它的性质,你不需要每次访问它都锁定。你会锁定一个互斥锁来发布到列表的末尾,当你认为你可能已经到达末尾时也会锁定。

您应该记录类中处理程序的数量,当您即将开始迭代时,您可以愉快地在不锁定的情况下进行迭代,直到达到这个数量。

如果处理程序将被删除,那么就会出现更多的线程争用问题。