【发布时间】:2014-01-04 22:09:28
【问题描述】:
我正在尝试用 Java 编写一个演员实现。我的设计需要一个高性能的地图数据结构来查找特定参与者被安排在哪个线程上。查找是使用 int id 完成的。所有演员都有单独的ID。我有以下要求:
i) 键是原始整数,而不是整数类。
ii) 值也是原语。值只能涵盖在数据结构实例化之前已知的少数数字。值只是线程/核心的 id,因此它可以是 short。线程数小于机器上的内核数,因此无法真正达到很高的数量。
iii)地图由单线程写入,但从多个线程读取。我希望我的实现是无锁的并且没有任何共享(虚假或其他)。因此读取不应涉及对非线程本地内存的任何写入。
iv) 写入次数(由单个线程)将大大超过来自使用映射进行查找的多个读取器线程的读取。
v) 需要的主要操作:
Set(key, value)和delete(key, value)始终从单个写入线程调用。大多数设置的键最终也会被删除,因此大量删除后的性能不会降低。将使用递增的整数生成新的键(actor-ids),并表示创建了一个actor。删除一个键(一个actor的id)表示该actor已经退出并且永远不会恢复。这也意味着一旦删除的密钥将永远不会再次设置。重要的是我们不要在地图中累积死区,因为会发生删除(演员退出)。Get(key)从阅读器线程调用。
vi) 操作get(key) 需要是eventually consistent,但有一些注意事项。假设编写器线程已将 key1->value1 对更改为 key1->value2。如果其中一个读取器执行 get(key1) 并且仍然收到 value1,这不是问题。最终它应该得到 value2。如果对 key1->value1 已被写入线程删除,并且读取线程上的 get(key1) 仍然返回 value1,也可以。实际上,我的意思是可以合并 Java 的 putOrderedObject/lazySet/getObjectVolatile 或 C++11 的 std::memory_order_relaxed/std::memory_order_acquire/std::memory_order_release 之类的东西。另一方面,如果确实设置了值,get(key1) 不应返回空值(例如 -1)。我不介意有一个getStrict(key1) 操作,如果get(key1) 返回一个空值来满足这个要求,我可以调用它。
我没有立即使用库的原因是:
i) Java 集合:它们需要包装类,因此不符合我的目标 (i) 和 (ii)
ii) Trove、FastUtil 等:它们确实有原始地图,但不提供任何并发访问设施。它们也没有针对稀疏范围内的值进行优化——在我的例子中是核心数。
iii) Java ConcurrentHashMap/ConcurrentSkipListMap:它们需要包装类并且不针对单个写入器、多个读取器的用例进行优化。
我意识到这些要求很多,所以如果答案解决了一些问题,而对其他一些问题保持模棱两可,那很好。将我指向设计中的源代码/代码或 cmets 会很棒。由于我正在尝试学习如何钓鱼,因此任何权衡取舍的解释都将是一个额外的好处。
如果我将我的详细要求归结为几个问题,它们可能是:
i) 如何针对单写入器/多读取器用例进行优化?
ii) 如何设计get(key) 和getStrict(key) 操作?这是正确的思考方式吗?
iii) 如何设计我的地图以利用递增键和稀疏值范围?
iv) 如何以最佳方式处理频繁删除?任何调整大小/重新散列都需要立即对阅读器线程可见,而不是最终可见。
也欢迎任何带有 C++/C++11 代码的答案。通过一些研究,我应该能够将大多数 std::atomic 操作转换为 Java Unsafe 操作。
【问题讨论】:
-
一个简单的读写锁怎么样,比如
std::shared_lock(C++14)? “高性能”不一定与“无锁”相关。无锁是关于确定性延迟而不是吞吐量。
标签: java c++ multithreading c++11