C++无锁线程问题 - 多个线程在一个连续数组上迭代,但从未访问过相同的成员数据?

问题描述 投票:0回答:1

在我的C++游戏引擎中,我有一个工作系统,利用工作线程来完成各种任务。线程与每一个可用的核心都有关系。最近,我一直试图通过最大化CPU利用率来优化我的一些系统管道。下面是一些伪代码的例子。它不是完全的复制品,但情况是相似的。

struct entityState {
  uint8 * byteBuffer; // Serialized binary data for the Entity
  uint8 * compressedData; // Compressed version of Entity data
  uint64  guid; // Unique ID
  gameTimeMS lastUpdated; // last time buffer was updated in milliseconds
  uint32 numUpdates; // Count of the number of updates
  uint32 numTimesAckedOverNetwork; // How many times client acked the data
  const char * typeData; // Type data in place of RTT
  bool markedForDelete; // Whether this object should be deleted next frame
  const char * debugData; // In debug configs, store meta data 
  // More member data but the point is made
};

// For examples sake, I have a contiguous array of entityState data
List< entityState * > entityStateList;
PopulateListWithEntityStateData(); // ~20,000 entityState ptrs on average
SortEntityStateList();
// Fire off 5 jobs each with their own worker thread
StartEntityStateJobs();

我就有5个作业,同时在这个列表上操作,而没有 互换式关键部分. 每个作业函数都会根据一个标准(如guid)通过二进制搜索访问数组,或者只是线性搜索。这里有一个问题。没有任何一个工作函数可以修改数组中实体状态ptrs的相同成员数据。实体状态列表. 然而,由于二进制搜索与线性搜索有碰撞,他们可以deference同一个实体状态ptr。但是,我重复一遍,它们从来不会在同一时间修改同一个成员数据。在每个线程上,没有成员数据ptr是同时被dereferenced的。

我已经用单元测试运行了这个模拟,没有遇到任何问题。但是,我有一些程序员朋友说,这样做有很小很小的概率会导致线程在dereferening同一个实体状态ptr时暂停和恢复的未定义行为。

我听到的另一个观点是,这种设置之所以能成功,是因为实体状态结构大小不适合放在一个缓存行中,最后将数据取值分割开来,由于结构数据被分割到不同的缓存行中,这本身就起到了数据保护的作用。说明一下,比如说上半部分适合在一个缓存行,下半部分适合在另一个缓存行,工作函数只对实体状态ptr的一个数据成员进行操作,而大部分时间恰好在不同的缓存行上。我没有在成员数据上使用任何原子修饰符或操作,因为没有任何作业会触及同一个成员数据。

最后,我也有一些程序员朋友说这是完全线程安全的。

然而,我有三种不同的说法,我对多线程的了解不够,无法确定哪种说法是正确的。

问题是......在野外发生'x'次中的1次的超低速崩溃有可能吗?即使是1100万也是不能接受的。这是不是一个安全。无锁 线程机制来并行执行对列表的多种操作?尽量忽略例子数据的琐碎性。在我的引擎例子中,它要复杂得多。这段代码可以运行在PC、Linux、游戏机等多个操作系统上。它还没有崩溃,但接触和测试的机会有限。我承认我不是一个低级专家,但这是节省了宝贵的性能时间。那么,我是在等着碰上地雷还是安全呢?编译器是gcc版本的C++11.另外,请避开locality的性能话题,除非它与线程和或线程安全有关。我知道缓存缺失是不好的。

问题 - 缝纫线是否安全?如果是或不是,请尽可能详细解释原因。我想加强我的低级知识。

c++ multithreading c++11 jobs lockless
1个回答
1
投票

@walnut已经详细解释了 "访问一个数组的不同元素保证不会引起数据竞赛"。

但是,你提到你有多个job函数更新实体状态,而且这些函数是由某个jobchain对象排序的。你没有详细介绍这个作业链是如何实现的,但你必须确保它建立一个正确的 之前 不同工作职能之间的关系,否则你 争分夺秒 国家成员.

我也同意@rustyx的观点--用ThreadSanitizer运行你的代码。它可以帮助揭示很多线程问题,包括数据竞赛。

© www.soinside.com 2019 - 2024. All rights reserved.