如何从多个线程并行安全地访问和写入复杂容器?

How to safely access and write to a complex container from multiple threads in parallel?

本文关键字:复杂 访问 线程 安全 并行      更新时间:2023-10-16

我有一个结构unordered_map的情况。该结构包含 int、布尔值和向量。我的程序将通过对服务器的https调用或使用websocket获取映射中每个项目的数据(map中的每个项目都需要单独的https调用(。使用 websocket 时,将一起返回地图中所有项目的数据。获取的数据被处理并存储在相应的向量中。

websocket 在单独的线程中运行,应该在程序的整个生命周期内运行。

我的程序有一个删除功能,可以"清空"整个地图。还有一个addItem((函数,它将向我的map添加新结构。

每当结构的 "updatesOn" 成员为 false 时,就不会将任何数据推送到向量中。

我当前的实现有 3 个线程:

  • 主线程将向地图添加新项目。主线程的另一个功能是从结构中的向量获取数据。主线程具有清空映射并重新开始的功能。它还有另一个只清空向量的函数。
  • 第二个线程将运行 WebSocket 客户端,并在新数据到达时填充结构中的 Vector。有一个 while 循环检查退出标志。在主线程中设置退出标志后,此线程将终止。
  • 第三个线程是管理器线程。它在map中查找新条目并进行http下载,然后将此项添加到websocket以进行后续数据更新。它还定期运行 http 下载,清空矢量并重新填充它。

现在我有两个互斥锁。

  • 一个用于在向量写入/读取数据之前锁定。
  • 第二个互斥锁是指在映射中添加或删除新数据。也可在清空地图时使用。

我觉得这是互斥锁的错误用法。正如我可能会在读取或写入其结构的矢量元素之一时清空地图。这使我对所有人使用一个互斥锁。

问题是这是一个实时股票数据程序,即每秒都会弹出新数据,有时甚至更快。恐怕一个互斥锁可能会减慢我的整个应用程序的速度。

如上所述,所有 3 个线程都对此映射具有写入访问权限,主线程能够将其清空完成。

牢记速度和线程安全,实现这一点的好方法是什么?

我的数据成员:

unordered_map<string, tickerDiary> tDiaries;
struct tickerDiary {
tickerDiary() : name(""), ohlcPeriodicity("minute"), ohlcStatus(false), updatesOn(true), ohlcDayBarIndex(0), rtStatus(false) {}
string name; 
string ohlcPeriodicity; 
bool ohlcStatus; 
bool rtStatus;
bool updatesOn;
int32 ohlcDayBarIndex;
vector<Quotation> data;
};
struct Quotation {
union AmiDate DateTime;
float   Price;
float   Open;
float   High;
float   Low;
float   Volume;
float   OpenInterest;
float   AuxData1;
float   AuxData2;
};

注意:我使用的是C++11。

如果我正确理解您的问题,您的地图本身主要编写在主线程中,其他线程仅用于对映射条目中包含的数据进行操作。

鉴于此,对于非主线程,有两个问题:

  1. 他们处理的项目不应随机消失
  2. 他们应该是唯一一个处理他们的项目的人。

第一个问题可以通过将存储与映射分离来最有效地解决。因此,对于每个项目,存储是单独分配的(通过默认分配器,或者如果您添加/删除项目很多,则通过某些池化方案(,并且地图仅存储共享 ptr。然后,处理项目的每个线程只需要保留一个共享的 ptr,以确保存储不会从它们下面消失。然后,仅在获取/存储/删除指针期间需要获取映射的关联互斥锁/shared_mutex。然后,只要可以接受某些线程可能会浪费一些时间对已从地图中删除的项目执行操作,就可以正常工作。使用 shared_ptrs 将确保您不会通过使用引用计数器泄漏内存,并且它们还将对这些引用计数进行锁定/解锁(或者更确切地说,尝试为这些引用使用更有效的平台原语(。如果你想了解更多关于shared_ptr和一般的智能指针,这是对智能指针的 C++ 系统的合理介绍。

这就留下了第二个问题,这个问题可能最容易通过在数据结构(tickerDiary(本身中保留互斥锁来解决,线程在开始执行需要结构中可预测行为的操作时获取该问题,并且可以在完成它们应该做的事情后释放。

以这种方式分离锁定应该可以减少映射的全局锁上的争用量。但是,您可能应该对您的代码进行基准测试,看看考虑到单个项目的分配和引用计数的额外成本,这种减少是否值得。

我认为使用std::vector不是正确的集合。但是,如果您坚持使用它,则每个集合应该只有一个互斥锁。

我建议使用英特尔 TBB 的concurrent_vector或 boost 的同步数据结构。

第三种解决方案可能是实现自己的并发向量