【问题标题】:Can you push synchronization costs onto one thread?你能把同步成本推到一个线程上吗?
【发布时间】:2015-06-01 22:57:12
【问题描述】:

我有两个线程:一个主线程执行应用程序的主要处理,一个辅助线程从主线程接收数据批并处理并将它们输出给用户、文件或通过网络。一般来说,数据的处理速度应该比它产生的速度快得多。我想确保主线程永远不会等待辅助线程。辅助线程可以接受任何数量的开销、扩展缓冲区、重做工作等,其唯一目标是最大化主线程的性能。理想情况下,主线程根本不会synchronize。有没有办法将同步成本推送到 Java 中的一个线程上?

【问题讨论】:

  • 您是否证明您遇到了阻塞问题?使用简单的LinkedBlockingQueue 不起作用吗?
  • 第一个线程没有理由等待第二个线程,除了访问用于交换数据的共享数据结构。这应该非常快,而且很少见,因为您将批量数据放入此数据结构,并且辅助线程很少从中获取项目(它大部分时间都在做 IO)。代码在哪里?您是否证明存在同步问题?
  • 现在程序正在设计中,我认为这个问题可能对大部分逻辑都在一个线程中的程序很有用。
  • Martin 做了一个关于单写单消费者的无锁队列的演讲,github.com/mjpt777/examples/tree/master/src/java/uk/co/…

标签: java multithreading synchronization overhead


【解决方案1】:

最简单的方法是 如果您的意思是主线程在发送数据时等待其他线程,那么没有大小限制的 BlockingQueue (LinkedBlockingQueu) 会阻止您的主线程“同步”成本。

【讨论】:

  • 我同意这是一种简单的方法,也是一个很好的起点。但这如何避免“同步”成本?
  • 来自问题“同步成本”似乎是指线程等待。主线程,写入队列时永远不应该等待。
【解决方案2】:

这是一个解决方案的大纲:

  1. 主线程独立工作一段时间,将数据堆积成一个集合;

  2. 当它生成了一个不错的批次时,它:

    我。为自己创建一个新集合;

    二。将已填满的集合放在一边,可供阅读线程拾取;

    三。将此集合 CAS 转换为 AtomicReference

  3. 阅读线程轮询此AtomicReference 以获取更新;

  4. 当它注意到它已被设置时,它会拿起批次,将null CASing 到共享引用中,以便主线程知道它可以放入另一个集合。

这对主线程的协调成本可以忽略不计(每个批次只有一个 CAS 操作),假设在共享新批次时引用始终已经是 null

读取线程可能会运行一个繁忙的循环来轮询共享引用,每次读取null 时都会休眠一小段时间。使线程休眠很短时间的最佳技术是

LockSupport.parkNanos(1);

它通常会休眠大约 30 µs,整个循环将消耗大约 2-3% 的 CPU 时间。当然,如果您想进一步降低 CPU 时间,您可以使用更长的暂停时间。

请注意,使线程在等待集中等待的协调技术会给双方带来非常大的延迟,因此如果 1 毫秒延迟对您来说是一个大问题,您应该远离它们。

【讨论】:

  • CAS 如何影响 CPU 加载排序缓冲区 (LOB) 和存储排序缓冲区 (SOB) 中的重新排序?
  • 我总是建议在审查 Java 程序的线程安全时不要考虑这么低的水平。如果你知道你有一个 happens before 边缘在你需要的地方,那么你就可以保证正确性。低级推理仅在考虑微秒级或更低级别的延迟时才进入。
  • 谢谢 Marko,这正是我想要验证的。 CAS 将为导致发布的批次发生的所有读取和写入设置一个发生在边缘之前。
  • 是的,除非您使用特殊用途的weakCompareAndSet,否则您将始终获得 happens-before 用于 Java 中的 CAS 操作。顺便说一句,在低级别,我认为所有 CPU 的 CAS 操作都保证了总排序(x86 肯定会这样做)。
猜你喜欢
  • 2013-11-25
  • 1970-01-01
  • 2017-12-21
  • 1970-01-01
  • 2020-09-19
  • 1970-01-01
  • 2012-04-25
  • 1970-01-01
相关资源
最近更新 更多