雙緩衝學習

lypbendlf發表於2024-06-23

轉自:https://gpp.tkchu.me/double-buffer.html,chatgpt,https://blog.51cto.com/u_15214399/4914060

1.介紹

定義緩衝類封裝了緩衝:一段可改變的狀態。 這個緩衝被增量地修改,但我們想要外部的程式碼將修改視為單一的原子操作。 為了實現這點,類儲存了兩個緩衝的例項:下一緩衝當前緩衝。 以下情況都滿足時,使用這個模式就很恰當:

  • 我們需要維護一些被增量修改的狀態。
  • 在修改到一半的時候,狀態可能會被外部請求。
  • 我們想要防止請求狀態的外部程式碼知道內部的工作方式。
  • 我們想要讀取狀態,而且不想等著修改完成。

關鍵點:

  1. 在狀態被修改後,雙緩衝需要一個swap步驟。 交換緩衝區的指標或者引用,速度快。 不管緩衝區有多大,交換都只需賦值一對指標。
  2. 這個模式的另一個結果是增加了記憶體的使用。 正如其名,這個模式需要你在記憶體中一直保留兩個狀態的複製。 在記憶體受限的裝置上,你可能要付出慘痛的代價。 如果你不能接受使用兩份記憶體,你需要使用別的方法保證狀態在修改時不會被請求。

雙緩衝解決的核心問題狀態有可能在被修改的同時被請求

2.例子

應用程式向磁碟寫入日誌,引入雙緩衝區機制,一個緩衝區儲存應用程式端傳送的日誌,按照時間順序依次儲存;另一個緩衝區負責向低層磁碟傳送寫檔案請求。雙緩衝區的奇妙之處就在於,兩個緩衝區的交換,是透過交換指標來實現的,非常的高效。

// 緩衝區1: 負責接收應用程式發來的日誌
LinkedList<String> currentBuffer = new LinkedList<>();
 
// 緩衝區2: 負責將資料同步到磁碟
LinkedList<String> syncBuffer = new LinkedList<>();

// 應用程式寫日誌,寫入第一個緩衝區
public void log(String content) {
    // 加鎖保證第一個緩衝區
    synchronized(this) {
        // 將log寫入記憶體緩衝中,這裡不會直接刷入磁碟檔案
        currentBuffer.add(content);
    }
 
    // 將緩衝區中的內容刷到磁碟
    logSync();
}

// 第二緩衝區,向磁碟寫日誌,並在寫入後交換緩衝區指標
private void logSync() {
    synchronized(this) {
        // 當前在刷記憶體緩衝到磁碟中去
        if (isSyncRunning) {
            // 判斷是否第二個緩衝區還在刷
            while (isSyncRunning) {
                try {
                    // 釋放鎖,即允許第一個緩衝區繼續接收日誌快取, 然後等待被喚醒
                    wait(2000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            // 此時沒有人在寫磁碟
        }
 
        // 交換緩衝區指標
        setReadyToSync();
 
        // 設定當前正在同步到磁碟的標誌位
        isSyncRunning = true;
    }
 
    // 刷磁碟,效能最低,不能加鎖
    logBuffer.flush();
 
    synchronized(this) {
        // 同步完磁碟之後,將標誌位復位
        isSyncRunning = false;
        // 喚醒其他等待刷磁碟的執行緒
        notifyAll();
    }
}

public void setReadyToSync() {
    LinkedList<String> tmp = currentBuffer;
    currentBuffer = syncBuffer;
    syncBuffer = tmp;
}

兩個緩衝區各自處理,互不干擾

兩個緩衝區很好的解決了應用程式的“快速、多執行緒”與IO操作的“緩慢,單執行緒”的矛盾。應該說,引入雙緩衝區是一個顯而易見的方式。

相關文章