Lecture 12: Concurrency 5

回顧:

  • 并行用餐哲學家
  • 讀者/作者問題

哲學家進餐問題

方案三:最大化并行

需要一個更復雜的解決方案來實現最大的并行性
解決方案使用:

state[N]:每個哲學家的當前狀態(THINKING, HUNGRY, EATING)

phil[N]:每個哲學家不是forks)專用的阻塞信號量初值為0。哲學家需要等待叉子時在此睡眠,被鄰座喚醒時再?sem_post

  • 如果他/她的鄰座正在吃飯,哲學家就會睡覺
  • 如果鄰座已經完成了同步:一個信號量/互斥鎖來強制臨界區的互斥(同時更新狀態),則喚醒哲學家

sync:一個信號量/互斥鎖強制臨界區互斥(同時更新狀態


  1. 思考 → 饑餓:哲學家想吃面時,先把 state[i] = HUNGRY,然后嘗試拿叉子。

  2. 拿叉子條件:只有當 左右鄰居都不是 EATING 時,才能把 state[i] 設為 EATING,否則就在 phi[i] 上睡眠。

  3. 吃完 → 思考:吃完后把 state[i] = THINKING,并檢查 左右鄰居 現在是否能吃(即它們是否處于 HUNGRY 且兩邊都沒人吃),如果可以就 sem_post 喚醒對應鄰居。

  4. 所有對 state[] 的讀寫 都必須先拿 sync 鎖,保證一致性。

哲學家只有在他/她的鄰座不吃東西的時候才能開始吃東西

全局數據結構

#define N 5
#define THINKING 1
#define HUNGRY 2
#define EATING 3int state[N] = {THINKING, THINKING, THINKING, THINKING, THINKING};
sem_t phil[N]; // sends philosopher to sleep
sem_t sync; // 互斥鎖,保護對 state[] 的讀寫

哲學家主循環

void * philosopher(void * id) {int i = *((int *) id);while(1) {printf("%d is thinking\n", i);take_forks(i); // 試圖拿叉子printf("%d is eating\n", i);put_forks(i); // 吃完放叉子}
}

拿叉子

void take_forks(int i)
{sem_wait(&sync);        // 進入臨界區state[i] = HUNGRY;      // 宣布“我餓了”test(i);                // 檢查現在能不能吃sem_post(&sync);        // 離開臨界區sem_wait(&phil[i]);     // 如果不能吃,就在 phil[i] 上睡眠
}

?放叉子

void put_forks(int i)
{int left  = (i + N - 1) % N;int right = (i + 1) % N;sem_wait(&sync);        // 進入臨界區state[i] = THINKING;    // 吃完,回到思考狀態test(left);             // 看左鄰居現在能不能吃test(right);            // 看右鄰居現在能不能吃sem_post(&sync);        // 離開臨界區
}

?核心輔助函數

void test(int i)
{int left  = (i + N - 1) % N;int right = (i + 1) % N;/* 只有當自己饑餓且左右鄰居都不在吃時,才能開吃 */if (state[i] == HUNGRY &&state[left]  != EATING &&state[right] != EATING) {state[i] = EATING;sem_post(&phil[i]);   // 喚醒等待的哲學家}
}

給每位哲學家配一個“狀態變量 + 專屬信號量”,通過“鄰居吃完后主動叫醒我”的局部喚醒策略,既保證互斥又允許不相鄰的人同時吃,從而無死鎖且達到最大并行度

讀者-作者問題

描述

  1. 場景
    并發進程(數據庫事務、文件訪問、I/O 設備等)分兩類:
    ? 讀者:讀取記錄(變量)——只讀數據,可多線程并行
    ? 寫者:寫入——要寫數據,必須獨占訪問,需要同步,不能與其他讀者或寫者并發。

  2. 三種同步方案
    ? 方案 1:樸素實現
    簡單粗暴地加一把大鎖:任何時候只允許一個讀者或一個寫者訪問,并行度極低
    ? 方案 2:讀者優先
    只要沒有寫者在寫,新來的讀者都可以立即讀;寫者可能長期挨餓
    ? 方案 3:寫者優先
    一旦有寫者請求,就盡快讓它寫;讀者可能長期挨餓

→ 核心權衡:提高并行度 vs. 避免饑餓,不同方案側重不同。

方案 1:無并行

void * reader(void * arg) {while(1) {pthread_mutex_lock(&sync);printf("reading record\n");pthread_mutex_unlock(&sync);}
}
void * writer(void * writer) {while(1) {pthread_mutex_lock(&sync);printf("writing\n");pthread_mutex_unlock(&sync);}
}
  • 不管讀者還是寫者,都先對同一把互斥鎖 sync 加鎖
    pthread_mutex_lock(&sync)

  • 拿到鎖以后才能 ,然后立即釋放鎖
    pthread_mutex_unlock(&sync)

  • 結果:
    任意時刻只有 一個線程(讀者或寫者)在訪問數據,完全沒有并行讀的優勢,并行度最低,但實現最簡單、絕不會出現數據競爭。

方案 2:讀者優先

  • 讀者可以并行:只要 iReadCount > 0,新讀者無需再搶 rwSync,直接讀即可。

  • 寫者可能餓死:如果讀者源源不斷,寫者將長期拿不到 rwSync

方案2的正確實現需要:

(1)iReadCount:一個跟蹤讀者數量的整數

  • 如果iReadCount > 0: writer被阻塞(sem_wait(rwSync))
  • 如果iReadCount == 0:寫入器被釋放(sem_post(rwSync))
  • 如果已經寫了,讀者必須等待

(2)sync: 對 iReadCount 本身的互斥鎖(初值 1),防止多個讀者并發修改計數。

(3)rwSync:寫者互斥鎖(初值 1)。同步讀者和寫者的信號量,由第一個/最后一個讀者設置。只要 iReadCount > 0正在寫,寫者就得在 rwSync 上阻塞。

① 讀者進入

sem_wait(&sync);      // 鎖住計數器
iReadCount++;         // 又來一個讀者
if (iReadCount == 1)  // 是第一個讀者?sem_wait(&rwSync);// 把寫者擋在外面
sem_post(&sync);      // 釋放計數器printf("reading record");

② 讀者離開

sem_wait(&sync);      // 再次鎖住計數器
iReadCount--;
if (iReadCount == 0)  // 最后一個讀者?sem_post(&rwSync);// 放行一個等待的寫者
sem_post(&sync);

③ 寫者

sem_wait(&rwSync);    // 等所有讀者和別的寫者退出
printf("writing");
sem_post(&rwSync);    // 寫完放行

方案3:寫者優先

它用了 4 個信號量 / 計數器 來確保:

  • 一旦有寫者到達,后續讀者必須排隊正在讀的讀者全部結束后立即讓寫者執行

  • 寫者之間互斥;

  • 寫者完成后,讀者再繼續。

優先考慮寫作者和使用者:(以下初值均為1)

  • 整數iReadCount和iWriteCount來跟蹤讀/寫的數量。
  • 互斥鎖sRead和sWrite來同步讀/寫的臨界區。
  • 信號量sReadTry在有寫入器等待時停止讀取器。讀者“入場券”。寫者到來后把它降為 0,阻塞新讀者。
  • 信號量sResource用于同步讀/寫資源。真正保護數據區的鎖。讀者或寫者要訪問數據必須先拿到它。

① 讀者

while (1) {sem_wait(&sReadTry);   // ① 先排隊拿“讀者入場券”sem_wait(&sRead);      // ② 鎖計數器iReadCount++;if (iReadCount == 1)   // ③ 第一個讀者搶數據鎖sem_wait(&sResource);sem_post(&sRead);      // ④ 解鎖計數器sem_post(&sReadTry);   // ⑤ 放行下一位讀者printf("reading\n");   // ⑥ 真正讀數據sem_wait(&sRead);      // ⑦ 讀完,更新計數器iReadCount--;if (iReadCount == 0)   // ⑧ 最后一個讀者釋放數據鎖sem_post(&sResource);sem_post(&sRead);
}

② 寫者

while (1) {sem_wait(&sWrite);     // ① 鎖寫者計數器iwriteCount++;if (iwriteCount == 1)  // ② 第一位寫者關閉“讀者入場券”sem_wait(&sReadTry);sem_post(&sWrite);sem_wait(&sResource);  // ③ 真正拿到數據鎖printf("writing\n");   // ④ 寫數據sem_post(&sResource);sem_wait(&sWrite);     // ⑤ 寫完,更新計數器iwriteCount--;if (iwriteCount == 0)  // ⑥ 最后一位寫者重新開放“讀者入場券”sem_post(&sReadTry);sem_post(&sWrite);
}

寫者一旦到達,先把 sReadTry 關閘,新讀者必須排隊;等所有正在讀的讀者走光后,寫者立即拿到 sResource;寫者全部完成后重新開閘,讀者才能繼續。從而實現了 寫者優先,讀者可能饑餓。















約束:

  • 每個人都樂于走進關燈的房間。
  • 讀者和作家不喜歡對方。
  • 讀者很樂意進入一個亮著綠燈而不是黃燈的房間。
  • 作家們很樂意進入一個黃燈而不是綠燈亮著的房間。
  • 只有讀者可以使用底部出口

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

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

相關文章

UniApp 微信小程序之間跳轉指南

概述 在UniApp開發中,經常需要實現從當前小程序跳轉到其他微信小程序的功能。本文檔詳細介紹了如何在UniApp中實現微信小程序之間的跳轉。 核心API uni.navigateToMiniProgram() 這是UniApp提供的用于跳轉到其他微信小程序的核心API。 基本語法 uni.navigateToMiniP…

基于SpringBoot+Vue的養老院管理系統的設計與實現 智能養老系統 養老架構管理 養老小程序

🔥作者:it畢設實戰小研🔥 💖簡介:java、微信小程序、安卓;定制開發,遠程調試 代碼講解,文檔指導,ppt制作💖 精彩專欄推薦訂閱:在下方專欄&#x1…

TRAE調教指南:用6A工作流項目規則+5S敏捷個人規則打造高效AI開發流程

TRAE調教指南:用6A工作流項目規則5S敏捷個人規則打造高效AI開發流程 引言:從"AI瞎寫"到"精準交付"的實戰手冊一、什么是Rules:讓AI"聽話"的底層邏輯1. 告別重復指令疲勞2. 實現"千人千面"的個性化適…

【C語言】gets和getchar的區別

在C語言中,gets和getchar是兩個用于輸入的標準函數,它們在功能和用法上有所不同。 功能上: gets函數主要用于讀取一行字符串,直到遇到換行符(回車鍵)為止。它會自動過濾掉換行符,不會將其讀入到…

【數據結構與算法】數據結構初階:詳解二叉樹(一)

🔥個人主頁:胡蘿卜3.0 🎬作者簡介:C研發方向學習者 📖個人專欄: 《C語言》《數據結構》 《C干貨分享》 ??人生格言:不試試怎么知道自己行不行 正片開始之前,我們來了解一下我們即…

工具測試 - marker (Convert PDF to markdown + JSON quickly with high accuracy)

參考鏈接如下:: 參考鏈接:https://github.com/datalab-to/marker?tabreadme-ov-file#llm-services 底層的OCR模型:https://github.com/datalab-to/surya 作用:開源免費🆓,多 GPU 推理、生成效…

STM32HAL 快速入門(七):GPIO 輸入之光敏傳感器控制蜂鳴器

STM32HAL 快速入門(七):GPIO 輸入之光敏傳感器控制蜂鳴器 前言 大家好,這里是 Hello_Embed。上一篇我們用 GPIO 輸入模式實現了按鍵控制 LED,本篇將進階到 “光敏傳感器控制蜂鳴器”—— 通過讀取光敏傳感器的信號&…

windows環境,安裝kafka

步驟 1: 準備工作 確保已安裝 Java:Kafka 需要 Java 運行時環境 (JRE) 或 Java 開發工具包 (JDK) 來運行。請確認您的系統上已安裝了 Java,并且 JAVA_HOME 環境變量正確配置。 解壓 Kafka:將下載的 Kafka 壓縮包解壓到一個目錄,比…

機器翻譯60天修煉專欄介紹和目錄

文章目錄 第一章:機器翻譯基礎認知與語言學鋪墊 第二章:經典機器翻譯模型(統計機器翻譯) 第三章:神經網絡基礎與詞向量技術 第四章:神經機器翻譯(NMT)基礎架構 第五章:NMT模型進階與訓練實踐 第六章:預訓練模型與機器翻譯應用 第七章:研究前沿與綜合項目 導論:學習…

openwrt增加自定義網頁

一. 簡介 本文介紹在OpenWRT中使用Luci框架定制設備配置頁面的方法,包括添加靜態頁面和參數配置頁面的過程,以及如何利用lua腳本實現界面與功能的結合。 二. Luci介紹 UCI 是 Openwrt 中為實現所有系統配置的一個統一接口,英文名 Unified Configuration Interface,即統一…

微服務的編程測評系統11-jmeter-redis-競賽列表

提示:文章寫完后,目錄可以自動生成,如何生成可參考右邊的幫助文檔 文章目錄前言1. 退出登錄1.1 后端1.2 前端2. 獲取當前用戶信息3. C端用戶競賽列表功能3.1 后端3.2 Jmeter-基本操作3.3 數據版本性能測試-壓力測試3.4 redis版本-緩存結構設計…

海濱浴場應急廣播:守護碧海藍天的安全防線

海濱浴場應急廣播:守護碧海藍天的安全防線!海濱浴場,是人們休閑娛樂、親近自然的理想場所。然而,變幻莫測的海洋環境也潛藏著諸多安全隱患,如溺水、離岸流、海蜇蜇傷、極端天氣等。為了有效應對突發事件,保…

華曦達港股IPO觀察丨以創新研發為筆,構建AI Home智慧生活新藍圖

深圳市華曦達科技股份有限公司自創立伊始,便將敏銳的市場洞察與前沿技術追蹤視為生命線。通過構建一支卓越的研發團隊,公司專注于自主核心技術的深耕與積累,以精密的硬件與創新的軟件筑起堅實的技術壁壘。其精心打造的“技術創新-…

構建現代化的Web UI自動化測試框架:從圖片上傳測試實踐說起

構建現代化的Web UI自動化測試框架:從圖片上傳測試實踐說起如何設計一個可維護、可擴展的Web UI自動化測試框架?本文通過一個圖片上傳測試實例,詳細介紹專業測試框架的搭建與實踐。當前測試框架結構 首先,讓我們了解一下當前的測試…

Apache IoTDB:大數據時代時序數據庫選型的技術突圍與實踐指南

摘要:時序數據庫在大數據時代迎來爆發式增長,IoTDB作為Apache頂級開源項目展現出顯著優勢:1. 性能卓越:支持千萬級數據點/秒寫入,18:1高壓縮比,查詢延遲低至500ms;2. 創新架構:采用樹…

2025年8月16日(星期六):雨騎古蓮村游記

清晨,當第一縷微光還未完全驅散夜幕的靜謐,我們這群由校長領銜的騎行愛好者已整裝待發。咖啡節早市尚未開攤,空氣中彌漫著一種期待與寧靜交織的氛圍,仿佛連時間都在為我們即將開啟的旅程而放慢腳步。今天的目標是古蓮村&#xff0…

Pandas數據預處理中缺失值處理

一、缺失值的概念表現形式1.數據庫中常用null表示2.部分編程語言中用NA表示3.可能表現為空字符串(‘’)或特定數值4.在Pandas中統一用NaN表示(來自NumPy庫,NaN、NAN、nan本質一致)NaN的特性1.與任何值都不相等&#xf…

計算機網絡:(十五)TCP擁塞控制與擁塞控制算法深度剖析

> 當網絡變成"堵城",TCP如何化身智能交通指揮家?揭秘百萬級并發背后的流量控制藝術! ### 一、生死攸關:為什么需要擁塞控制? **真實災難案例**:1986年勞倫斯伯克利實驗室網絡大崩潰,因缺乏擁塞控制導致全網癱瘓36小時。TCP擁塞控制由此誕生,核心解決**資…

python中的單下劃線“_”與雙下劃線“__”的使用場景及“左右雙下劃線”(魔術方法:`__xxx__`)

在Python中,單下劃線“_”和雙下劃線“__”的使用場景和含義有顯著區別,主要體現在命名約定和語法 一、單下劃線“_”的使用場景 單下劃線更多是編程約定(而非強制語法),用于傳遞特定的“暗示”,不影響代碼…

我們為什么需要時序數據庫?

引言在當今數據驅動的世界中,時間序列數據正以前所未有的速度增長。從物聯網設備傳感器、金融交易記錄到應用程序性能監控,時間序列數據無處不在。傳統的關系型數據庫在處理這類數據時往往力不從心,這時時序數據庫(Time Series Database, TSD…