我只在 Intel 的 Thread Building Blocks 和 Microsoft 的 Parallel Patterns Library 中使用过 concurrent_vector 之类的经验,但我认为它们可能与 Java 的 Vector 相当。
根据我的测试,两者中的concurrent_vector 都比大多数替代方法慢很多,例如执行多个线程并将结果收集到本地、线程不安全的容器中(如 C++ 中的std::vector),然后将结果附加到锁内的共享集合,或者创建一个列表列表,每个线程写入自己的列表(使用一些数组索引),然后以串行方式将结果组合到最后的单个列表中。
据我所知,线程安全版本的好处是方便。即使从英特尔自己的库中,我也从未找到并发的、线程安全的随机访问序列,我无法用线程不安全的替代方案击败它,我在一个线程中本地累积结果,然后使用一些基本的线程同步并组合结果在一个线程中以串行方式。如果我的测试是写繁重的,那么我通常可以使用这种粗略的方法比并发容器快 2-3 倍的结果。
也就是说,在许多情况下,您的时间实际上倾向于将元素写入/附加到容器中,这种情况可能很少见。更常见的是,我发现我在现实世界中的大部分线程案例都花在阅读和处理数字等上,而只有一小部分时间花在将结果推送到容器的后面。所以很多时候并发容器的开销开始变得可以忽略不计,而且它肯定更方便,而且更不容易跨线程滥用。
在可能很少见的情况下,写入容器是并行算法所花费时间的很大一部分,在我的测试和经验中,并发容器永远不会为粗糙的替代方案提供性能优势。因此,如果它是代码的性能特别关键的部分,并且您在分析会话中看到并发容器的方法中的热点,我会尝试替代方法,包括在线程本地容器中累积输出而不是并发(即线程本地ArrayList),然后在算法结束时从一个线程或锁/关键部分以串行方式组合它们的所有结果(例如:组合ArrayList)。
不幸的是,要使事情既线程安全又具有最大可扩展性是很棘手的。您可以通过在一个线程中执行所有内容来使架构中的事物最大程度地线程安全。线程安全解决了!但这根本无法利用并行性进行扩展。我发现这样的并发性——一种平衡的行为,与阿姆达尔定律相悖。我发现并发容器处于中等位置,因为使用它们几乎总是会在某种程度上牺牲最佳性能,但最佳和非最佳之间的差异可能可以忽略不计。好吧,你测量并看到我所看到的。
至于你的这部分问题:
但由于多个线程修改和访问它,我认为
数据的完整性可能无法得到保障
从我的角度来看,这与设计有关。自从我从理论的角度思考计算机科学以来已经有很多年了,但是这里有一个隐含的假设,即必须共享这些数据。根据用户端的需求,数据可能需要也可能不需要共享。拿一个视频游戏来访问来自scene 的数据。渲染引擎和物理引擎等似乎都必须共享同一个场景。这很直观。这很符合人情味。
但情况不一定如此。从用户端的角度来看,渲染器是否有自己的场景副本用于将结果渲染到屏幕上可能并不重要,这可能与正在发生的其他事情略有不同步。所以很多时候,至少在需要最佳性能或帧速率或最短等待时间时,您可以复制/复制数据以允许线程在没有任何线程同步的情况下尽可能快地运行(包括排除原子操作)需要访问共享数据。无论是否使用持久数据结构等,这都会变得非常复杂。这取决于设计。但我认为多线程编程的一个违反直觉的方面是,我们首先倾向于认为需要在线程之间共享的东西比它们真正需要共享的东西更多,并且放弃这个假设可以产生全新程度的并行性.我与我的许多同事和我自己都发现,触手可及的并发容器会诱使我们在线程之间共享比我们真正需要的更多的数据。如果我们的目标是尽可能有效地利用硬件,那么尽可能多地放弃共享数据的概念通常是有意义的,包括这些并发容器。如果您的软件与游戏引擎一样对性能至关重要,我实际上建议尽量减少它们的使用。