簡體   English   中英

帶翻轉緩沖區的無鎖容器

[英]Lock-free container with flipping buffers

對於我的一個必須支持並發讀取和寫入的項目,我需要一個能夠緩沖項目的容器,直到消費者一次獲取每個當前緩沖的項目。 由於生產者應該能夠產生數據而不管消費者是否讀取當前緩沖區我想出了一個自定義實現,在AtomicReference的幫助下,將每個條目添加到支持ConcurrentLinkedQueue直到執行翻轉,導致當前條目在存儲具有空隊列和元數據的新條目時返回,以原子方式存儲在該AtomicReference

我想出了一個解決方案,例如

public class FlippingDataContainer<E> {

  private final AtomicReference<FlippingDataContainerEntry<E>> dataObj = new AtomicReference<>();

  public FlippingDataContainer() {
    dataObj.set(new FlippingDataContainerEntry<>(new ConcurrentLinkedQueue<>(), 0, 0, 0));
  }

  public FlippingDataContainerEntry<E> put(E value) {
    if (null != value) {
      while (true) {
        FlippingDataContainerEntry<E> data = dataObj.get();
        FlippingDataContainerEntry<E> updated = FlippingDataContainerEntry.from(data, value);
        if (dataObj.compareAndSet(data, updated)) {
          return merged;
        }
      }
    }
    return null;
  }

  public FlippingDataContainerEntry<E> flip() {
    FlippingDataContainerEntry<E> oldData;
    FlippingDataContainerEntry<E> newData = new FlippingDataContainerEntry<>(new ConcurrentLinkedQueue<>(), 0, 0, 0);
    while (true) {
      oldData = dataObj.get();
      if (dataObj.compareAndSet(oldData, newData)) {
        return oldData;
      }
    }
  }

  public boolean isEmptry() {
    return dataObj.get().getQueue().isEmpty();
  }
}

由於需要將當前值推送到后備隊列,因此現在需要格外小心。 from(data, value)方法的當前實現看起來像這樣:

static <E> FlippingDataContainerEntry<E> from(FlippingDataContainerEntry<E> data, E value) {
  Queue<E> queue = new ConcurrentLinkedQueue<>(data.getQueue());
  queue.add(value);
  return new FlippingDataContainerEntry<>(queue,
      data.getKeyLength() + (value.getKeyAsBytes() != null ? value.getKeyAsBytes().length : 0),
      data.getValueLength() + (value.getValueAsBytes() != null ? value.getValueAsBytes().length : 0),
      data.getAuxiliaryLength() + (value.getAuxiliaryAsBytes() != null ? value.getAuxiliaryAsBytes().length : 0));
}

由於可能由其他線程在該線程執行更新之前更新值引起的重試,我需要在每次寫入嘗試時復制實際隊列,否則即使原子引用不能,也會將條目添加到共享隊列中” t 得到更新。 因此,簡單地將值添加到共享隊列可能導致值條目被多次添加到隊列中,而實際上它只應該出現一次。

復制整個隊列是一項非常昂貴的任務,所以我只修改了當前隊列,而不是在from(data, value)方法中復制隊列,而不是將 value 元素添加到執行時塊中的共享隊列發生更新:

public FlippingDataContainerEntry<E> put(E value) {
  if (null != value) {
    while (true) {
      FlippingDataContainerEntry<E> data = dataObj.get();
      FlippingDataContainerEntry<E> updated = FlippingDataContainerEntry.from(data, value);
      if (data.compareAndSet(data, updated)) {
        updated.getQueue().add(value);
        return updated;
      }
    }
  }
  return null;
}

from(data, value)我現在只設置隊列而不直接添加值元素

static <E> FlippingDataContainerEntry<E> from(FlippingDataContainerEntry<E> data, E value) {
  return new FlippingDataContainerEntry<>(data.getQueue(),
      data.getKeyLength() + (value.getKeyAsBytes() != null ? value.getKeyAsBytes().length : 0),
      data.getValueLength() + (value.getValueAsBytes() != null ? value.getValueAsBytes().length : 0),
      data.getAuxiliaryLength() + (value.getAuxiliaryAsBytes() != null ? value.getAuxiliaryAsBytes().length : 0));
}

雖然與復制隊列的代碼相比,這允許測試運行速度快 10 倍以上,但它也經常無法通過消耗測試,因為現在在消費者線程翻轉隊列並處理之后可能會立即將值元素添加到隊列中數據,因此並非所有項目似乎都被消耗了。

現在的實際問題是,是否可以避免復制后備隊列以獲得性能提升,同時仍然允許使用無鎖算法自動更新隊列的內容,從而避免在中途丟失一些條目?

首先,讓我們陳述顯而易見的 - 最好的解決方案是避免編寫任何此類自定義類。 也許像java.util.concurrent.LinkedTransferQueue這樣簡單的東西也能正常工作,而且不容易出錯。 如果LinkedTransferQueue不起作用,那么LMAX 干擾器或類似的東西呢? 您是否查看過現有的解決方案?

如果您仍然需要/想要自定義解決方案,那么我有一個略有不同的方法的草圖,可以避免復制:

這個想法是讓put操作圍繞一些原子變量旋轉,試圖設置它。 如果一個線程設法設置它,那么它獲得對當前隊列的獨占訪問權,這意味着它可以附加到它。 追加后,它重置原子變量以允許其他線程追加。 它基本上是一個自旋鎖 這樣,線程之間的爭用發生在追加到隊列之前,而不是之后。

我需要在每次寫入嘗試時復制實際隊列

你的想法聽起來像 RCU ( https://en.wikipedia.org/wiki/Read-copy-update )。 通過為您解決釋放問題(我認為),Java 被垃圾收集使 RCU 更容易。

如果我從快速瀏覽您的問題中正確理解,那么您的“讀者”實際上想為自己“聲明”容器的全部當前內容。 這也使他們成為有效的編寫者,但他們可以構建一個空容器,而不是讀取 + 復制,並以原子方式交換頂級引用以指向該容器。 (因此要求舊容器進行獨占訪問。)

RCU 的一大好處是容器數據結構本身不必到處都是原子的; 一旦你引用了它,其他人就不會修改它。


唯一棘手的部分是當作者想要向非空容器添加新內容時。 然后您復制現有容器並修改副本,並嘗試將 CAS(比較交換,即compareAndSet() )更新后的副本放入共享的頂級AtomicReference

作家不能只是無條件地交換,因為它可能最終得到一個非空的容器並且無處可放。 除非作者可以繼續處理一批工作並旋轉等待讀者清空隊列......


我在這里假設您的作家有成批的工作要立即排隊; 否則 RCU 對作家來說可能太貴了。 抱歉,如果我錯過了您的問題中排除了這一點的細節。 我不經常使用 Java,所以我只是快速寫一下,以防萬一。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM