【问题标题】:Performance of single-writer, different reader vs same thread单写者的性能,不同的读者与相同的线程
【发布时间】:2019-12-31 23:03:58
【问题描述】:

我有一个“订单管理器”应用程序,我遵循“单一编写者”方法来处理订单事件以避免任何竞争条件。

所以我有一个主线程,它忙于为传入的订单事件旋转并处理事件而不进行任何阻塞调用。

这个主线程还需要静态数据来验证订单事件等。所以我将静态数据存储在内存缓存(java 映射)中。

我想了解通过我的主线程访问内存中静态数据的最高效方式。

A) 主线程本身是否应该负责维护内存中的静态数据,以便所有事情都只有一个线程,从而实现最大 CPU 吞吐量,避免任何与线程相关的争用/锁定。

或者

B) 我是否应该使用单独的专用线程将静态数据存储在 ConcurrentHashmap 中,并与我的主线程(单个写入器,不同的读取器线程)共享此映射。我在某处读到,只要有一个写入线程,读取线程的访问就会使用特殊通道跨 CPU 内核进行优化。但同样,使用第二种方法,我仍然必须使用 ConcurrentHashMap,它会带来与线程和锁相关的延迟。

我想听听关于什么是实现最大吞吐量的最佳方法的看法。

【问题讨论】:

  • 你检查过ReadWriteLock吗?最大吞吐量是在没有同步的情况下实现的,但代价是潜在的过时数据。然后就是什么时候被哪个线程访问了……

标签: java multithreading performance


【解决方案1】:

听起来像是经典的事件流处理。一个线程更简单,通常性能更高(线程间通信总是有一些成本)。如果有一个 CPU 密集型任务可以并发运行,那么在单独的线程中执行一项工作会带来好处,这在订单流处理中通常不是这种情况。

还可以查看 LMAX Disruptor 或 Real Logic Aeron 的想法。

【讨论】:

  • 感谢 Kan。事实上,我正在使用 LMAX Disruptor 进行订单事件处理(单个业务消费者线程从环形缓冲区读取)。正如您所说的,与静态数据缓存相关的更新不是 CPU 密集型的,也不频繁,这就是为什么我不热衷于使用单独的线程来维护该数据。因此,其中一个选项是我将静态数据更新也发送到环形缓冲区,并让我的主线程将它们存储在映射中,从而避免在订单事件处理期间发生任何线程间通信。
  • @saurabh.in 将更新发送到另一个环形缓冲区也是一种线程间通信,也不是免费的(例如,缓冲区本身是一个内存和原子偏移量)。但是,很难说成本与数据操作成本有何不同——它对您的应用程序来说非常具体。所以,唯一正确的方法——做实验。衡量性能,尝试改变,再次衡量性能,然后选择更好的。
  • 实际上我的意思是将静态数据事件发送到用于订单事件的同一个环形缓冲区。这样,我的单个主线程将获得顺序以及静态数据事件,并将以不同方式处理它们(对于静态数据,它将简单地将它们存储在哈希图中)。因此,一切都将由一个线程处理。静态数据更新很少,比如每天 10 次,所以我的主线程 99% 的时间都在服务订单事件。此外,AFAIK 环形缓冲区不涉及锁定,但我肯定会阅读您提到的原子偏移量。
  • @saurabh.in 我会投票赞成这种将静态数据发送到同一个缓冲区并维护简单哈希映射的方法。如果你做一个共享的并发地图 - 快速做起来并不容易。例如。 ConcurrentHashMap 使用锁,因此您应该避免在事件流中使用这种数据结构。原子通常更快,但它们仍然强制读取内存等,而单线程访问可能更积极地使用 CPU 缓存。
  • 关于在此应用程序中使用 hazelcast,我已经单独提出了一个相关问题。很高兴看看你是否也有这方面的 cmets - stackoverflow.com/q/59579079/3049693
【解决方案2】:

线程间共享状态的方式不止两种。而且几乎不可能说出哪种更适合您的情况。正确的方法是对几个解决方案(jmh、jcstrees)进行基准测试。

由于简单、维护成本等原因,如果单线程能够提供足够的性能,则认为单线程是最好的解决方案。 但是,如果我们在一般情况下试图获得最大吞吐量,那么多线程是必不可少的。

如果计算/维护静态数据的成本太高,我会选择第二种方法加一个。我会考虑使用发布不可变映射的解决方案,而不是 CHM。

【讨论】:

    【解决方案3】:

    这取决于缓存中静态数据的性质。如果在您的应用程序工作时该数据没有更改,那么您可以在主线程中创建和填充该缓存并在同一个主线程中使用它。 但是,如果需要更新该缓存(定期或作为对某些事件的响应),那么最好有一个单独的线程创建缓存,最初填充它然后进行更新,因为如果您开始在主线程中进行缓存更新,那么在缓存更新完成之前,该主线程将无法处理订单。

    【讨论】:

    • 缓存是静态的,不会经常更改 - 一天可能会更改 10 次。所以我正在考虑让主线程在获取静态数据相关更新时将其维护在地图中。从外部系统的传入事件中提取静态数据所需的小处理可以由一个单独的线程处理,然后该线程可以将准备存储的数据放入我的主线程可以读取的环形缓冲区(lmax 中断器)中并存储在哈希图中。
    猜你喜欢
    • 1970-01-01
    • 2020-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-08-03
    相关资源
    最近更新 更多