如何在没有同步方法的情况下从不同的线程安全地刷新缓冲区?

Tho*_*mas 6 java multithreading synchronized

有多个线程,比如B,C和D,每个线程都以高频率将小数据包写入缓冲区.他们拥有自己的缓冲区,没有其他人写过它.写作必须尽可能快,我已经确定使用synchronized它会让它变得无法接受.

缓冲区只是字节数组,以及第一个自由元素的索引:

byte[] buffer;
int index;

public void write(byte[] data) {
    // some checking that the buffer won't overflow... not important now
    System.arraycopy(data, 0, buffer, index, data.length);
    index += data.length;
}
Run Code Online (Sandbox Code Playgroud)

每隔一段时间,线程A就会将每个人的缓冲区刷新到文件中.如果这部分有一些开销可以,所以synchronized在这里使用是没有问题的.

现在麻烦的是,一些其他线程可能正在写入缓冲区,而线程A正在刷新它.这意味着两个线程会index在同一时间尝试写入.这会导致数据损坏,我想阻止,但使用synchronizedwrite()方法.

我已经感觉到,使用正确的操作顺序,可能还有一些volatile领域,这一定是可能的.有什么好主意吗?

Jon*_*eet 7

您是否尝试过使用同步的解决方案,并发现它的性能不够好?你说你已经确定它的速度慢得令人无法接受 - 速度有多慢,你是否已经有了性能预算?通常情况下,获得无争议的锁是非常便宜的,所以我不认为这是一个问题.

有可能是一些聪明的无锁的解决方案-但它很可能是显著比每当你需要访问共享数据只是同步更加复杂.我知道无锁编码风靡一时,并且在你可以做到的时候可以很好地扩展 - 但如果你有一个线程干扰另一个数据,那么很难安全地进行编码.为了清楚起见,当我可以使用专家创建的高级抽象时,我喜欢使用无锁代码 - 比如.NET 4中的Parallel Extensions.我只是不喜欢使用像volatile这样的低级抽象,如果我可以帮忙.

尝试锁定并对其进行基准测试.找出可接受的性能,并将简单解决方案的性能与该目标进行比较.

当然,一种选择是重新设计......冲洗是否必须在不同的线程中主动发生?个别编写器线程是否可以定期将缓冲区切换到刷新线程(并启动不同的缓冲区)?这会让事情变得简单得多.

编辑:关于你的"冲洗信号"的想法 - 我一直在思考类似的问题.但是你需要注意你是如何做到的,这样即使一个线程需要很长时间来处理它正在做的事情,信号也不会丢失.我建议你让线程A发布一个"刷新计数器"......并且每个线程在最后一次刷新时保留自己的计数器.

编辑:刚刚意识到这是Java,而不是C# - 更新:)

用于AtomicLong.incrementAndGet()从线程A递增,以及AtomicLong.get()从其他线程读取.然后在每个线程中,比较您是否"最新",并在必要时刷新:

private long lastFlush; // Last counter for our flush
private Flusher flusher; // The single flusher used by all threads 

public void write(...)
{
    long latestFlush = flusher.getCount(); // Will use AtomicLong.get() internally
    if (latestFlush > lastFlush)
    {
        flusher.Flush(data);
        // Do whatever else you need
        lastFlush = latestFlush; // Don't use flusher.getCount() here!
    }
    // Now do the normal write
}
Run Code Online (Sandbox Code Playgroud)

请注意,这假设您只需要在Write方法中检查是否需要刷新.显然情况可能并非如此,但希望你能适应这个想法.

  • @bestsss:当然,无锁编程很有趣也很有趣......但它也很难,而且我不愿意在生产中实现我自己的无锁算法.至于"写一个单一的易变性" - 你需要提供更多的细节,我怀疑.我期待你的答案. (2认同)