C++(11) "High Performance" 并发单个写入器

C++(11) "High Performance" Concurrent Single Writer(s)

本文关键字:并发 单个写 Performance High C++      更新时间:2023-10-16

我有一个对象B,由10个双值组成。双值由一个传感器产生,该传感器与并每1-2毫秒发送一次更新。B的实例必须在程序中的其他两个位置使用做一些计算和可视化。

在另一个线程中,使用另一个传感器每隔4-10毫秒更新一个对象A,该对象包含约1000个双值。

两个对象都有一个时间签名(当传感器的更新到达时,通过使用boost::chrono::high_resolution_clock获取)

现在我想使用两个对象AB,这两个对象在同时计算对象CCD_ 7的一些值。这应该在两个线程都在运行时完成,并且输出用于进行一些可视化、计算平均值等。整个程序运行1-2小时之后仅使用计算出的CCD_ 8s的实例,不再需要CCD_ 9和CCD_。

建议使用什么方法(或设计模式)来实现线程之间的通信和数据共享?

目前,整个结构实施得很糟糕构成CCD_ 11的线程与构成B的线程直接通信而不使用诸如互斥锁之类的同步方法。

  • 我应该使用无锁双端队列来存储AB吗并从构成CCD_ 15的线程中读取
  • 我应该使用像boost::signals2这样的(线程安全)观察者模式来"发送"AB的实例吗
  • 还是其他什么

我假设您在没有实时保证的PC和操作系统上进行计算。所以,只要你在毫秒范围内,你就不会受到严格的实时限制。因此,使用无锁定数据结构并不是绝对必要的。但你可以。他们的表现也很好。如果你愿意的话,你可以使用boosts-flock-free数据结构。但是,您可能需要一个pop()函数来阻塞,直到队列不为空,因为否则您需要进行繁忙等待,或者使用某种信号量或条件变量来等待队列不为空闲的状态。

您的争用率很低,而且只有几个线程。在低争用下锁定互斥体通常需要大约25ns。所以这不是问题。如今,内存分配通常也非常快,通常平均不到100ns。我的看法是:

  • 使用std::queue<std::packaged_task<void()>>作为任务队列。如果计算结果不需要std::future s,您也可以决定将std::function<void()>而不是std::packaged_task<void()>()排入队列。使用std::mutex保护对它的访问。有一个std::condition_variable在那里等待队列非空。我曾经在github上实现了一个通用的阻塞并发队列,它比这更高效,但只使用标准的库方法。您只需要将模板参数指定为T=std::packaged_task<void()>,就可以获得所需的数据结构。

  • 创建一个只执行传入任务的线程std::thread。要退出线程,请在线程的最后一个任务中设置一些std::atomic<bool>标志,这样线程就会停止执行,您可以优雅地join()它。同样,您可以将该功能放入一个类中。类的析构函数应该告诉任务执行线程停止并执行join()。我在github上的同一个存储库中实现了这样一个类。你可以自由使用。

  • 现在,您可以从接收硬件数据的两个线程将任务传递给任务执行器。只需将适当的lambda传递给执行需要完成的工作的任务执行器。如果你决定使用我的实现,你可以写一些类似的东西

    void receiveData( const Data & data, cu::ParallelExecutor & executor )
    {
        executor.addTask( [=]{ /* Do something with `data` */ } );
    }
    

这样,一切都是线程安全的,具有低开销和高性能。