史上最清楚!讀者,寫者問題(操作系統os)

讀者-寫者問題是另一個里程碑式的同步互斥問題。它比生產者-消費者更復雜,因為它引入了不對稱的訪問權限:讀者和讀者之間是共享的,但寫者和任何人(包括讀者和其他寫者)之間都是互斥的。

我們用一個生動的比喻來解析這個經典問題:一個公共閱覽室里的一本珍貴孤本

  • 共享文件:閱覽室里唯一的一本珍貴孤本
  • 讀者進程:只想閱讀這本書的人。他們只是看,不會損壞書。
  • 寫者進程:想要修訂或批注這本書的人。他們的操作會改變書的內容。

1. 規則分析:閱覽室的“規章制度”

為了保護這本孤本,閱覽室管理員制定了以下四條嚴格的規定:

  1. 允許多個讀者同時讀:只要書沒被修訂,可以有很多個讀者圍在一起同時看。這不影響什么。
  2. 只允許一個寫者寫:任何時候,最多只能有一個修訂者在書上寫字。
  3. 寫者工作時,不許任何人打擾:如果一個修訂者正在工作,那么任何讀者都不能來看,任何其他修訂者也不能來寫。必須保證修訂過程的絕對獨立。
  4. 有讀者在看時,寫者必須等待:如果已經有一群讀者在看書,那么新來的修訂者必須在門外等待,直到所有讀者都離開。

2. 初步嘗試:最簡單的“一刀切”方案

最簡單的管理辦法就是:閱覽室一次只許進一個人,不管你是讀者還是寫者

  • 實現:設置一個互斥信號量?rw,初始值為1。
    • P(rw):進門前申請。
    • V(rw):出門后歸還。
  • 問題:這個方案雖然安全,但效率極低。它違反了規則1,明明可以多個讀者一起看,現在卻變成了讀者也要一個個排隊。這大大降低了閱覽室的利用率。

3. 核心突破:引入“讀者計數器”

為了實現“允許多個讀者同時讀”,我們需要知道當前有多少個讀者在閱覽室里

  • 思路:我們引入一個整數變量?count,初始為0,用來記錄讀者數量。
    • 第一個讀者到來時,他有特殊的責任:他需要負責把閱覽室的門鎖上,防止任何寫者進來。
    • 后續的讀者到來時,發現門已經被第一個讀者鎖了(為讀者們鎖的),他們只需要把?count?加一,然后直接進去就行。
    • 讀者離開時,也需要把?count?減一。
    • 最后一個讀者離開時,他也有特殊的責任:他需要負責把閱覽室的門打開,好讓在外面等待的寫者能有機會進來。

4. 再次碰壁:計數器本身的“互斥”問題

上面的思路很好,但在并發環境下,對?count?的“讀-改-寫”操作(比如?if(count==0)?然后?count++)不是原子的!

  • 場景

    1. 讀者A執行?if(count==0),發現條件成立。
    2. 此時發生切換,讀者B也執行?if(count==0),發現條件也成立。
    3. 結果:A和B都認為自己是“第一個”讀者,他們倆都去執行了“鎖門”操作(P(rw))。第一個人鎖成功了,第二個人就會被永遠鎖在門外。
  • 解決方案:引入一個新的互斥信號量?mutex,初始值為1,專門用來保護對?count?變量的訪問。任何想修改?count?的人,都得先拿到?mutex?這把“鑰匙”。

5. “讀者優先”的解決方案(會導致寫者餓死)

結合以上思路,我們得到了第一個可行的、但有缺陷的方案。

semaphore rw = 1;      // 用于實現讀寫互斥,也叫“閱覽室大門鎖”
semaphore mutex = 1;   // 用于保護count變量的互斥鎖
int count = 0;         // 讀者計數器reader() {P(mutex);          // 1. 鎖住計數器,準備修改if (count == 0) {  // 2. 如果我是第一個讀者P(rw);         //    就負責鎖上閱覽室大門,阻止寫者}count++;           // 3. 讀者數量加一V(mutex);          // 4. 解鎖計數器// --- 臨界區 ---讀文件...// --- 臨界區 ---P(mutex);          // 5. 鎖住計數器,準備修改count--;           // 6. 讀者數量減一if (count == 0) {  // 7. 如果我是最后一個讀者V(rw);         //    就負責打開閱覽室大門,允許寫者進入}V(mutex);          // 8. 解鎖計數器
}writer() {P(rw);             // 1. 鎖上閱覽室大門// --- 臨界區 ---寫文件...// --- 臨界區 ---V(rw);             // 2. 打開閱覽室大門
}
  • 問題所在(寫者餓死)
    • 假設一個寫者正在等待?P(rw)。此時,閱覽室里至少有一個讀者(count>0),所以?rw?鎖被占著。
    • 如果在這個時候,不斷有新的讀者到來。他們可以成功通過?P(mutex),把?count?從1變成2,從2變成3... 他們根本不需要去碰那把被寫者等待的?rw?鎖。
    • 結果就是,只要有任何一個讀者在閱覽室里,后續源源不斷的讀者流都可以直接進入,而那個可憐的寫者,就永遠等不到?count?變成0的那一刻,永遠也進不了門。這就是“讀者優先”導致的“寫者餓死”。

6. 最終解決方案:引入“寫者優先”機制(讀寫公平法)

為了解決寫者餓死的問題,我們需要一個機制,當一個寫者在等待時,后續新來的讀者不能“插隊”。

  • 思路:我們再增加一把“門外的大門鎖”?w,初始值為1。
    • 寫者想寫時,先鎖上這把?w?鎖。
    • 讀者想讀時,也得先嘗試獲取?w?鎖。
  • 這樣,一旦一個寫者在等待(即他已經成功執行了P(w),但在等P(rw)),那么后續所有新來的讀者都會被?P(w)?擋在門外,無法插隊。他們只能等這個寫者完成工作,釋放了?w?鎖之后,才能和其他等待的讀者一起公平競爭。

最終的、讀寫相對公平的解決方案:

semaphore rw = 1;
semaphore mutex = 1;
semaphore w = 1;       // 新增的“寫者優先”鎖
int count = 0;reader() {P(w);              // (新增) 在讀者隊列外再加一道關卡P(mutex);if (count == 0) {P(rw);}count++;V(mutex);V(w);              // (新增) 讀者進入后立刻釋放w,允許其他讀者或寫者排隊讀文件...P(mutex);count--;if (count == 0) {V(rw);}V(mutex);
}writer() {P(w);              // 1. 寫者優先獲取w鎖P(rw);             // 2. 再獲取文件鎖寫文件...V(rw);V(w);              // 3. 寫完后,釋放兩把鎖
}
  • 效果分析
    • 這個方案并非絕對的“寫者優先”,因為它不會搶占已經在讀的讀者。
    • 它實現的是一種相對公平的排隊機制。當一個寫者開始排隊(執行P(w))后,后續新來的讀者也必須排在他后面,等待?w?鎖。這保證了先來后到的公平性,避免了寫者饑餓。因此也被稱為讀寫公平法

現在,我們來對這個被稱為“讀寫公平法”的最終解決方案,進行一次詳細、清晰的流程化文字介紹。

算法名稱

讀寫公平法?(亦常被某些教材描述為一種“寫者優先”的實現思路,但其效果更接近于公平排隊)。

算法目標

在解決讀者-寫者問題的基礎上,額外解決“讀者優先”方案中可能出現的寫者饑餓問題。其核心思想是:當一個寫者已經表示了想要寫入的意圖后,后續新到達的讀者不能“插隊”搶先進入,必須等待該寫者完成操作。

所需的信號量和變量

  1. int count = 0;

    • 角色:讀者計數器。
    • 作用:記錄當前正在讀取共享文件的讀者進程數量。
    • 初始值:0,表示初始時沒有讀者。
  2. semaphore mutex = 1;

    • 角色:互斥信號量。
    • 作用:專門用于保護共享變量?count?的訪問。確保對?count?的檢查和修改操作是原子的,防止多個讀者并發修改?count?時出錯。
    • 初始值:1,表示?count?的“修改權”可用。
  3. semaphore rw = 1;

    • 角色:互斥信號量。
    • 作用:用于實現讀者與寫者之間、寫者與寫者之間的互斥。可理解為共享文件本身的“讀寫鎖”。一旦被加鎖,只有持有鎖的進程(或一群讀者)可以訪問文件。
    • 初始值:1,表示文件當前可供訪問。
  4. semaphore w = 1;

    • 角色:同步/互斥信號量(關鍵所在)。
    • 作用:這是一個實現“公平排隊”的關鍵信號量。它可以被看作是一個在所有讀者和寫者之上的、更高級別的“通行證”。它保證了當一個寫者正在等待時,后續的讀者無法越過它。
    • 初始值:1,表示“通行證”可用。

讀者進程的詳細執行流程

一個讀者進程想要讀取文件,需要執行以下步驟:

  1. 申請“排隊資格” (P(w)):

    • 在嘗試讀取之前,首先要申請w這個“通行證”。如果此時有一個寫者正在寫或正在等待,那么w鎖很可能已經被占用,該讀者就會在此處被阻塞,進入一個統一的等待隊列。這一步確保了讀者不會插隊到已在等待的寫者前面。
  2. 鎖住計數器 (P(mutex)):

    • 成功通過w關卡后,為了安全地修改讀者計數器count,進程需要先獲取mutex鎖。
  3. 判斷并鎖住文件(如果是第一個讀者):

    • 檢查count的值。如果count == 0,說明自己是當前第一個到達的讀者。作為“先鋒”,它有責任通過執行P(rw)來鎖住共享文件,以阻止任何寫者進入。
  4. 更新計數器 (count++):

    • count加一,表明現在閱覽室里又多了一位讀者。
  5. 解鎖計數器 (V(mutex)):

    • count的修改已經完成,立刻釋放mutex鎖,以便其他(已通過w關卡的)讀者可以進來更新count
  6. 釋放“排隊資格” (V(w)):

    • 這是非常關鍵的一步。在進入讀操作之前,讀者會立刻釋放w鎖。這使得在它自己正在讀書時,其他進程(無論是讀者還是寫者)可以繼續競爭w鎖并排隊。如果不釋放,就會變成一次只允許一個進程(或一批讀者)通過,大大降低并發性。
  7. 執行讀操作 (reading is performed):

    • 這是讀者的臨界區。此時,文件鎖rw已經被第一個讀者鎖上,可以安全地讀取文件,并且允許多個讀者同時處于這個階段。
  8. 鎖住計數器(準備離開) (P(mutex)):

    • 讀完后,準備離開。再次獲取mutex鎖,以安全地修改count
  9. 更新計數器 (count--):

    • count減一,表明有一位讀者離開了。
  10. 判斷并解鎖文件(如果是最后一個讀者):

    • 檢查count的值。如果count == 0,說明自己是最后一個離開的讀者。作為“殿后”者,它有責任通過執行V(rw)來解開文件鎖,以便在外等待的寫者可以進入。
  11. 解鎖計數器 (V(mutex)):

    • count的修改完成,釋放mutex鎖。讀者進程的整個流程結束。

寫者進程的詳細執行流程

一個寫者進程想要寫入文件,流程相對簡單,但權力更大:

  1. 申請“排隊資格” (P(w)):

    • 與讀者一樣,寫者首先也需要獲取w這個“通行證”。一旦它成功獲取了w(或正在等待w),就能有效阻止新來的讀者插隊。
  2. 鎖住文件 (P(rw)):

    • 獲取了w之后,接著申請對文件的“獨占寫權限”,即rw鎖。此時,如果仍有讀者在文件內(即rw鎖被讀者們持有),寫者會在此處被阻塞,直到最后一個讀者離開并執行V(rw)
  3. 執行寫操作 (writing is performed):

    • 這是寫者的臨界區。此時,寫者同時持有了w鎖和rw鎖,保證了沒有任何其他讀者或寫者可以進入。
  4. 解鎖文件 (V(rw)):

    • 寫操作完成,首先釋放文件鎖rw
  5. 釋放“排隊資格” (V(w)):

    • 最后釋放w鎖,允許在w上等待的其他進程(可能是讀者也可能是寫者)繼續競爭。

通過這一套精密的、由多個信號量協同工作的流程,該算法在保證數據一致性的前提下,既允許多個讀者并發讀取,又通過一個公平的排隊機制,有效避免了寫者進程被餓死的問題。


必會題與詳解

題目一:在“讀者優先”的解決方案中,互斥信號量mutex的作用是什么?如果去掉它會發生什么?

答案詳解

  1. mutex的作用:互斥信號量?mutex?在這里的作用是保護共享變量?count?的訪問。對?count?的操作,如“檢查?count?是否為0”和“count++”,在邏輯上必須是一個原子操作。P(mutex)?和?V(mutex)?將這兩步操作捆綁在一起,確保在任何時刻只有一個讀者能修改?count?的值。

  2. 去掉mutex的后果:如果去掉?mutex,兩個讀者進程可能會并發地執行對?count?的修改,導致?count?值不正確,并可能引發死鎖或互斥失效

    • 一個典型的錯誤場景
      1. 初始時?count = 0
      2. 讀者A執行?if (count == 0),判斷為真。
      3. 此時發生進程切換,讀者B執行?if (count == 0),判斷也為真。
      4. 讀者A繼續執行,執行?P(rw)?成功,然后?count++count?變為1。
      5. 切換回讀者B,它也執行?P(rw)。但此時?rw?已經被A鎖住,因此讀者B被永久阻塞在了?P(rw)?這里,因為它錯誤地認為自己是第一個讀者,而去嘗試獲取一個已經被占用的鎖。

題目二:在最終的“讀寫公平”解決方案中,新增的信號量?w?是如何解決寫者饑餓問題的?

答案詳解

信號量?w?解決寫者饑餓問題的核心機制是建立了一個在所有讀者和寫者之上的、統一的“排隊關卡”

  1. 形成排隊:無論是讀者還是寫者,在真正嘗試訪問文件(或?count?變量)之前,都必須先通過?P(w)?這一關。這相當于在閱覽室的大門口設置了一個取號機,保證了先來后到的基本順序。

  2. 阻止讀者插隊

    • 當沒有寫者等待時,w?鎖是開著的,讀者可以自由通過。
    • 當一個寫者到來并開始等待時,它會首先執行?P(w)?并成功獲取?w?鎖(或者被阻塞在?w?上)。一旦?w?鎖被某個寫者持有或等待,任何后續新來的讀者在執行它們自己的?P(w)?時,都會被阻塞。
    • 這就阻止了讀者無限插隊的情況。新來的讀者必須等到當前正在等待的寫者(以及在他之前排隊的進程)完成操作、釋放了?w?鎖之后,才有機會進入。
  3. 實現公平:通過這種方式,w?保證了當一個寫者已經“掛號”等待后,系統不會無視他而去服務那些后來的讀者。這大大提高了寫者被服務的機會,避免了饑餓,實現了讀寫進程間相對公平的競爭。

題目三:讀者-寫者問題和生產者-消費者問題在本質上有什么不同?

答案詳解

兩者都是經典的同步互斥問題,但它們處理的“關系”有本質的不同。

  1. 關系對稱性不同

    • 生產者-消費者問題對稱的。生產者之間是互斥的,消費者之間也是互斥的(因為它們都要操作同一個緩沖區指針或計數器),生產者和消費者之間也是互斥的。所有進程對緩沖區的訪問都是完全互斥的。
    • 讀者-寫者問題不對稱的。讀者和讀者之間是共享的(可以并發),而寫者與讀者、寫者與寫者之間都是互斥的。這種不對稱性使得其邏輯比前者復雜得多。
  2. 解決核心不同

    • 生產者-消費者的核心是資源(產品和空位)的計數與同步。它主要通過兩個同步信號量(full?和?empty)來協調生產者和消費者的步調,防止從空緩沖區取或向滿緩沖區放。
    • 讀者-寫者的核心是身份識別與權限管理。它需要區分進程是“讀者”還是“寫者”,并根據身份賦予不同的訪問權限。其解決方案的核心是一個?count?計數器,用來動態地判斷“第一個讀者”和“最后一個讀者”,從而實現復雜的、有條件的加鎖和解鎖。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/88935.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/88935.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/88935.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

使用Starrocks替換Clickhouse的理由

背景 Starrocks和clickhouse都是非常優秀的OLAP數據庫,那么什么情況下使用clickhouse,什么場景下使用starrocks呢,本文就簡單列舉一下他們的優缺點 理由 首先兩者都是列存儲,并且都實現了列壓縮,所以從存儲中兩者的壓縮…

Mybatis 兩級緩存可能導致的問題

Mybatis 兩級緩存可能導致的問題兩級緩存簡介一級緩存 localCache效果開關二級緩存兩級緩存可能導致的問題分布式環境下查詢到過期數據事務隔離級別失效讀已提交失效讀未提交失效總結兩級緩存簡介 一級緩存 localCache 效果 一級緩存是 session 或者說事務級別的&#xff0c…

vue3+uniapp 使用vue-plugin-hiprint中實現打印效果

前言: vue3uniapp 使用vue-plugin-hiprint中實現打印效果 官網地址:gitee https://gitee.com/ccsimple/vue-plugin-hiprinthttps://gitee.com/ccsimple/vue-plugin-hiprint 實現效果: 預覽打印內容: 實現步驟: 1、安…

【elementUI踩坑記錄】解決 el-table 固定列 el-table__fixed 導致部分滾動條無法拖動的問題

目錄一、問題背景二、 問題現象三、核心原因四、解決辦法增強方案🚀寫在最后一、問題背景 在使用 Element UI 的 el-table 組件時,固定列功能雖然實用,但會引發滾動條交互問題: 固定列區域懸浮顯示滾動條但無法正常拖動滾動條 …

【機器人編程基礎】python文件的打開和關閉

文件的打開和關閉 在Python中,文件操作是一項基本而重要的任務,涉及到打開、讀取、寫入、關閉文件等操作。正確地管理文件對于數據持久化、輸入輸出處理等至關重要。下面將詳細解釋如何在Python中打開和關閉文件,并提供相應的代碼示例。 文件打開 在Python中,可以使用內…

ShenYu實戰、問題記錄

概述 一款高性能的國產的Apache開源API網關,官方文檔。 在ShenYu v2.6.1, ShenYu注冊中心只支持http類型,中間件注冊類型已經被移除。 所以,請使用http注冊類型來注冊你的服務。不是微服務注冊中心,它只是將元數據、選擇器數據、…

走近科學IT版:EasyTire設置了ip,但是一閃之后就變回到原來的dhcp獲得的地址

EasyTier 是一款簡單、安全、去中心化的內網穿透和異地組網工具,適合遠程辦公、異地訪問、游戲加速等多種場景。無需公網 IP,無需復雜配置,輕松實現不同地點設備間的安全互聯。 上次實踐的記錄:適合遠程辦公、異地訪問的EasyTier…

rk3588平臺USB 3.0 -OAK深度相機適配方法

目錄 文件更改記錄表 1、usb規則添加 2、拉取相關依賴 3、安裝python3、安裝pip 4、安裝依賴 5、安裝ffmeg 6、攝像頭功能測試 7、將視頻拷貝到U盤查看 1、usb規則添加 由于OAK是USB設備,因此為了在使用 udev 工具的系統上與之通信, 您需要添加udev規則以使…

工廠模式總結

工廠模式1. 簡單工廠模式&#xff08;Simple Factory&#xff09; 核心思想 定義一個工廠類&#xff0c;根據輸入參數創建不同的具體對象。客戶端不直接調用具體類的構造函數&#xff0c;而是通過工廠類獲取對象。 示例代碼 #include <iostream> #include <memory>…

MySQL的三種安裝方式(mis、zip、yum)

目錄 2.0數據庫安裝 2.1windows上.mis格式 環境準備 MySQL的安裝 環境配置&#xff08;非必要&#xff09; 2.2windows上.zip格式安裝 環境準備 配置文件的內容 MySQL的安裝 附錄可能出現問題 圖形工具遠程連接數據庫 2.3Linux上安裝yum包 環境準備 過程命令 My…

串口學習和藍牙通信HC05(第八天)

&#x1f468;?&#x1f4bb;個人主頁&#xff1a;開發者-削好皮的Pineapple! &#x1f468;?&#x1f4bb; hello 歡迎 點贊&#x1f44d; 收藏? 留言&#x1f4dd; 加關注?! &#x1f468;?&#x1f4bb; 本文由 削好皮的Pineapple! 原創 &#x1f468;?&#x1f4b…

設計總監的“輕量化”新武器:用Adobe Express,音頻一鍵驅動動畫

在快節奏的創意項目中&#xff0c;如何將復雜的設計理念或冗長的研究報告&#xff0c;快速轉化為易于理解、富有吸引力的動態內容&#xff0c;是衡量一個團隊溝通效率的關鍵。作為一名在海外設計界工作了十余年的設計師&#xff0c;我發現&#xff0c;最高效的團隊&#xff0c;…

零知開源——STM32F407VET6驅動SHT41溫濕度傳感器完整教程

?零知開源是一個真正屬于國人自己的開源軟硬件平臺&#xff0c;在開發效率上超越了Arduino平臺并且更加容易上手&#xff0c;大大降低了開發難度。零知開源在軟件方面提供了完整的學習教程和豐富示例代碼&#xff0c;讓不懂程序的工程師也能非常輕而易舉的搭建電路來創作產品&…

Linux流量分析:tcpdump wireshark

前言 最近因為工作需要&#xff0c;研究了下如何使用tcpdump和wireshark分析業務流量。如果要使用tcpdump分析具體的HTTP請求耗時&#xff0c;需捕獲網絡數據包并分析時間戳信息&#xff0c;重點關注TCP連接的建立、HTTP請求發送到響應接收的全過程。 以下是具體步驟和技巧&…

深度學習圖像分類數據集—角膜潰瘍識別分類

該數據集為圖像分類數據集&#xff0c;適用于ResNet、VGG等卷積神經網絡&#xff0c;SENet、CBAM等注意力機制相關算法&#xff0c;Vision Transformer等Transformer相關算法。 數據集信息介紹&#xff1a;角膜潰瘍識別分類&#xff1a;[dot, mix, slice] 訓練數據集總共有270張…

功能強、超好用【PDF轉換工具】的介紹下載與安裝教程

Windows 電腦上一款簡單好用的PDF轉換工具&#xff0c;可以輕松地將其他文檔轉換為 PDF 格式&#xff0c;也可以將 PDF 文件轉換為其他格式&#xff0c;如常見的 Word、Excel、PPT 等。 此外軟件還支持 Office 文檔合并分割、旋轉頁面、拼接頁面、刪除文字、刪除頁面、添加水印…

c# 釘釘應用實現監聽審批事件以及獲取審批結果的流程

oa的操作已經測試了一遍 image.png如果是自建oa則代表發起的審批是跳轉網頁&#xff0c;否則釘釘打開后是一個表單界面&#xff0c;不需要調整自己搞得oa。 所以我感覺目前公司的需求更適合官方oa 表單來填寫,更靈活&#xff0c;還支持用戶配置。 但是用戶點了審批&#xff0c;…

Typecho架構深度剖析:輕量級博客系統的設計哲學與實現原理

文章目錄 深度解析Typecho:輕量級博客系統的架構設計與實現1. Typecho概述與技術背景1.1 發展歷程1.2 核心特性2. 系統架構設計分析2.1 核心架構圖2.2 核心組件3. 核心模塊實現分析3.1 路由系統實現3.2 數據庫抽象層4. 插件系統深度解析4.1 Hook機制實現4.2 插件開發示例5. 性…

LangChain 內存(Memory)

1. 為什么需要內存&#xff1f; 大型語言模型&#xff08;LLM&#xff09;本身是無狀態的。這意味著每次你向 LLM 發送一個請求&#xff08;Prompt&#xff09;&#xff0c;它都會獨立處理這個請求&#xff0c;完全不記得之前任何的交互。這在構建一次性問答應用時沒問題&#…

基于定制開發開源AI智能名片S2B2C商城小程序的社群游戲定制策略研究

摘要&#xff1a;本文聚焦社群游戲定制領域&#xff0c;深入探討以社群文化和用戶偏好為導向的定制策略。通過分析互動游戲活動、社群文化塑造等關鍵要素&#xff0c;結合定制開發開源AI智能名片S2B2C商城小程序的技術特性&#xff0c;提出針對性游戲定制方案。研究旨在提升社群…