同步互斥與通信

目錄

一、同步與互斥的概念

二、同步與互斥并不簡單

三、各類方法的對比


一、同步與互斥的概念

一句話理解同步與互斥:我等你用完廁所,我再用廁所。

什么叫同步?就是:哎哎哎,我正在用廁所,你等會。

什么叫互斥?就是:哎哎哎,我正在用廁所,你不能進來。

同步與互斥經常放在一起講,是因為它們之的關系很大,“互斥”操作可以使用“同步”來 實現。我“等”你用完廁所,我再用廁所。這不就是用“同步”來實現“互斥”嗎?

再舉一個例子。在團隊活動里,同事A先寫完報表,經理B才能拿去向領導匯報。

經理B必須等同事A完成報表,AB之間有依賴,B必須放慢腳步,被稱為同步。在團隊活動中,同 事A已經使用會議室了,經理B也想使用,即使經理B是領導,他也得等著,這就叫互斥。經 理B跟同事A說:你用完會議室就提醒我。這就是使用"同步"來實現"互斥"。

01 void 搶廁所(void)
02 {
03     if (有人在用) 我瞇一會;
04     用廁所;
05     喂,醒醒,有人要用廁所嗎;
06 }

假設有A、B兩人早起搶廁所,A先行一步占用了;B慢了一步,于是就瞇一會;當A用完后叫醒B,B也就愉快地上廁所了。

在這個過程中,A、B是互斥地訪問“廁所”,“廁所”被稱之為臨界資源。我們使用了“休眠-喚醒”的同步機制實現了“臨界資源”的“互斥訪問”。

同一時間只能有一個人使用的資源,被稱為臨界資源。比如任務A、B都要使用串口來打印,串口就是臨界資源。如果A、B同時使用串口,那么打印出來的信息就是A、B混雜, 無法分辨。所以使用串口時,應該是這樣:A用完,B再用;B用完,A再用。

二、同步與互斥并不簡單

在裸機程序里,可以使用一個全局變量或靜態變量實現互斥操作,比如要互斥地使用 LCD,可以使用如下代碼:

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     if (bCanUse)
05     {
06         bCanUse = 0;
07         /* 使用LCD */
08         bCanUse = 1;
09         return 0;
10     }
11     return -1;
12 }

但是在 RTOS 里,使用上述代碼實現互斥操作時,大概率是沒問題的,但是無法確保萬無一失。

假設如下場景:有兩個任務 A、B 都想調用 LCD_PrintString,任務 A 執行到第 4 行代碼時發現 bCanUse 為 1,可以進入 if 語句塊,它還沒執行第 6 句指令就被切換出去了;然后任務 B 也調用 LCD_PrintString,任務 B 執行到第 4 行代碼時也發現 bCanUse 為 1,也可以進入 if 語句塊使用 LCD。在這種情況下,使用靜態變量并不能實現互斥操作

上述例子中,是因為第 4、第 6 兩條指令被打斷了,那么如下改進:在函數入口處先然讓 bCanUse 減一。這能否實現萬無一失的互斥操作呢?

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     bCanUse--;
05     if (bCanUse == 0)
06     {
07         /* 使用LCD */
08         bCanUse++;
09         return 0;
10     }
11     else
12     {
13         bCanUse++;
14         return -1;
15     }
16 }

把第 4 行的代碼使用匯編指令表示如下:

04.1 LDR R0, [bCanUse] ????// 讀取bCanUse的值,存入寄存器R0

04.2 DEC R0, #1 ????????????????// 把R0的值減一

04.3 STR R0, [bCanUse]? ? // 把R0寫入變量bCanUse

假設如下場景:有兩個任務 A、B 都想調用 LCD_PrintString,任務 A 執行到第 04.1 行代碼時讀到的 bCanUse 為 1,存入寄存器 R0 就被切換出去了;然后任務 B 也調用LCD_PrintString,任務 B 執行到第 4 行時發現 bCanUse 為 1 并把它減為 0,執行到第 5 行代碼時發現條件成立可以進入 if 語句塊使用 LCD,然后任務 B 也被切換出去了;現在任務 A 繼續運行第 04.2 行代碼時 R0 為 1,運行到第 04.3 行代碼時把 bCanUse 設置為 0,后續也能成功進入 if 的語句塊。在這種情況下,任務 A、B 都能使用 LCD。

上述方法不能保證萬無一失的原因在于:在判斷過程中,被打斷了。如果能保證這個過程不被打斷,就可以了:通過關閉中斷來實現。

示例 1 的代碼改進如下:在第 5~7 行前關閉中斷。

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     disable_irq();        //關閉中斷
05     if (bCanUse)
06     {
07         bCanUse = 0;
08         enable_irq();    //開啟中斷
09         /* 使用LCD */
10         bCanUse = 1;
11         return 0;
12     }
13     enable_irq();        //開啟中斷
14     return -1;
15 }

示例 2 的代碼改進如下:在第 5 行前關閉中斷。

01 int LCD_PrintString(int x, int y, char *str)
02 {
03     static int bCanUse = 1;
04     disable_irq();
05     bCanUse--;
06     enable_irq();
07     if (bCanUse == 0)
08     {
09         /* 使用LCD */
10         bCanUse++;
11         return 0;
12     }
13     else
14     {
15         disable_irq();
16         bCanUse++;
17         enable_irq();
18         return -1;
19     }
20 }

關閉中斷的方法并不是萬無一失的:假設現在有任務A和任務B在執行以下函數,在A打印后過了1ms,B被調度,但B只是進行判斷,但會一直失敗,過了1msA被調度繼續打印,打印完后過了1ms輪到B,B也是繼續判斷然后一直失敗,這樣子導致的結果是B占用了cpu資源,與同步例子類似,那么此時解決方法是:將B執行時若A已經在打印了,將B設置為阻塞狀態,于是A繼續打印,等到A打印完畢,將B喚醒,B才能正常打印。

void CalTask(void *params)//計時函數
{uint32_t i = 0;time = system_get_ns();//程序執行到此的時間for(i=0;i<10000000;i++){sum += i;}Cal_end = 1;//計算標志位置1time = system_get_ns() - time;//先計算程序到此的時間 再減去之前的時間 得到for循環中的時間vTaskDelete(NULL);
}void LcdPrintTask(void *params)
{int len;while(1){/* 打印信息 */LCD_PrintString(0,0,"waitting");vTaskDelay(2000);//讓此任務進入阻塞狀態 等上面任務執行完畢在執行這里 讓下面的while死循環不會占用cpu資源 不調用此函數則不會進入阻塞 會一直死等 為同步例子while(Cal_end == 0);//這里是在等待上面的計數完畢 若不使用上一行的vTaskDelay函數 則會在這里死等 占用cpu資源 因為兩個任務是交叉執行 若沒有上一行的vTaskDelay函數 燒錄完發現時間為2s左右 由于兩個任務叫交叉執行 若將B任務刪除只執行A任務 則時間差不多為一半即1ms 那么此時就可以調用vTaskDelay函數讓此任務進入阻塞狀態 讓task1先執行 執行完畢計時標志位置1 同時vTaskDelay函數計時完畢后進入此時的死循環 此時計時標志位置1直接跳出循環 不會讓程序卡在這里占用cpu資源 就可以準確計算出程序執行時間if(flag){flag = 0;LCD_ClearLine(0,0);len = LCD_PrintString(0,0,"Sum:");//這里的返回值是打印任務的長度len += LCD_PrintHex(len,0,sum,1);//將打印名字處后打印":"的長度一起加上 得到的長度是打印完任務名字和":"后的長度LCD_ClearLine(0,2);len = LCD_PrintString(0,2,"Time(ms):");len += LCD_PrintSignedVal(len,2,time/1000000);flag = 1;}vTaskDelete(NULL);}
}/* FreeRTOS.c中創建任務 */
xTaskCreate(CalTask,"taskA",128,NULL,osPriorityNormal,NULL);
xTaskCreate(LcdPrintTask,"taskB",128,&Task2,osPriorityNormal,NULL);

同步例子(任務B沒有阻塞)

由結果可知,兩個任務程序是交叉進行的,在RTOS中,任務級相同的任務每隔1s交叉運行,所以任務A執行1s(開始計時)后切到任務B,而A沒執行完畢 flag 始終為 0,那么任務B就會死等(任務A沒執行完畢,仍在計時) flag 為1,當B執行完A繼續執行(繼續計時)后 flag 為1后進入任務B才能成功計時完畢。可見時間大約為2s,若只有程序A執行,那么時間大約為1s左右。

互斥例子(任務B阻塞)

任務A不斷計時,任務A執行完1s后任務B執行,而任務B中調用了阻塞函數,所以繼續輪到任務A執行,任務A執行完畢跳轉到任務B中B成功打印計時值。(這里的計數值主要來源于任務A,所以若任務A執行完畢,會停止計時,所以B中的阻塞延時(只是2s后延時)對于計時結果無影響,為了能夠更好準確的得到計時值)

三、各類方法的對比

能實現同步、互斥的內核方法有:任務通知(task notification)、隊列(queue)、事件組 (event group)、信號量(semaphoe)、互斥量(mutex)。

它們都有類似的操作方法:獲取/釋放、阻塞/喚醒、超時。比如:

  • 任務 A 獲取資源,用完后任務 A 釋放資源
  • 任務 A 獲取不到資源則阻塞,任務 B 釋放資源并把任務 A 喚醒
  • 任務 A 獲取不到資源則阻塞,并定個鬧鐘;A 要么超時返回,要么在這段時間內因為任務 B 釋放資源而被喚醒。

通過對比的方法來區分:

  • 能否傳信息?還是只能傳遞狀態?
  • 為眾生(所有任務都可以使用)?只為你(只能指定任務使用)?
  • 我生產,你們消費?
  • 我上鎖,只能由我開鎖
內核對
生產
消費
數據/狀態
說明
隊列
ALL
ALL
數據:若干個數據
誰都可以往隊列里扔數據,
誰都可以從隊列里讀數據
用來傳遞數據,
發送者、接收者無限制,
一個數據只能喚醒一 個接收者
事件組
ALL
ALL
多個位:或、與
誰都可以設置(生產)多個 位,
誰都可以等待某個位、若 干個位
用來傳遞事件,

可以是 N 個事件,

發送者、接受者無限制,
可以喚醒多個接收 者:像廣播
信號量
ALL
ALL
數量:0~n
誰都可以增加一個數量,
誰都可消耗一個數量
用來維持資源的個數,
生產者、消費者無限
制,
1 個資源只能喚醒 1
個接收者
任務通知
ALL
只有我
數據、狀態都可以傳輸,
使用任務通知時,
必須指定接受者
N 對 1 的關系:
發送者無限制,
接收者只能是這個任
互斥量
A 上鎖
只能 A 開鎖
位:0、1
我上鎖:1 變為 0,
只能由我開鎖:0 變為 1
就像一個空廁所,
誰使用誰上鎖,
也只能由他開鎖

使用圖形對比如下:

  • 隊列:
    • 里面可以放任意數據,可以放多個數據
    • 任務、ISR 都可以放入數據;任務、ISR 都可以從中讀出數據
  • 事件組:
    • 一個事件用一 bit 表示,1 表示事件發生了,0 表示事件沒發生
    • 可以用來表示事件、事件的組合發生了,不能傳遞數據
    • 有廣播效果:事件或事件的組合發生了,等待它的多個任務都會被喚醒
  • 信號量:
    • 核心是"計數值"
    • 任務、ISR 釋放信號量時讓計數值加 1
    • 任務、ISR 獲得信號量時,讓計數值減 1
  • 任務通知:
    • 核心是任務的 TCB 里的數值
    • 會被覆蓋
    • 發通知給誰?必須指定接收任務
    • 只能由接收任務本身獲取該通知
  • 互斥量:
    • 數值只有 0 或 1
    • 誰獲得互斥量,就必須由誰釋放同一個互斥量

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

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

相關文章

【實戰場景】記一次UAT jvm故障排查經歷

【實戰場景】記一次UAT jvm故障排查經歷 開篇詞&#xff1a;干貨篇&#xff1a;1.查看系統資源使用情況2.將十進制進程號轉成十六進制3.使用jstack工具監視進程的垃圾回收情況4.輸出指定線程的堆內存信息5.觀察日志6.本地環境復現 總結篇&#xff1a;我是杰叔叔&#xff0c;一名…

線下促銷折扣視頻介紹

千呼新零售2.0系統是零售行業連鎖店一體化收銀系統&#xff0c;包括線下收銀線上商城連鎖店管理ERP管理商品管理供應商管理會員營銷等功能為一體&#xff0c;線上線下數據全部打通。 適用于商超、便利店、水果、生鮮、母嬰、服裝、零食、百貨、寵物等連鎖店使用。 詳細介紹請…

Linux上systemctl 和 service 兩個命令的區別和聯系

systemctl 和 service 兩個命令都是 Linux 系統中用于管理服務的工具&#xff0c;但它們分別關聯著不同的初始化系統&#xff08;init system&#xff09;&#xff0c;并且在功能和使用場景上有所差異。 service 命令 關聯的初始化系統&#xff1a;service 命令通常與 SysV i…

Python從零學習筆記(1)

1pip無法調用 剛入python&#xff0c;需要用到第三方模塊&#xff0c;但是按照教程使用>>>pip install 總是出現錯誤提示 網上查詢許久&#xff1a;語句沒錯&#xff1b;安裝沒錯&#xff1b;環境配置也正常 最后才知道是不能先進入python模式&#xff0c;而是使用p…

2024年道路運輸安全員考試題庫及答案

一、多選題 11.《放射性物品安全管理條例》規定&#xff0c;運輸放射性物品時&#xff0c;應當使用專用的放射性物品運輸包裝容器。在運輸過程中正確的做法有&#xff08; &#xff09;。 A.托運人和承運人應當按照國家放射性物品運輸安全標準和國家有關規定&#xff0c;在…

什么是定時器?

前言&#x1f440;~ 上一章我們介紹了阻塞隊列以及生產者消息模式&#xff0c;今天我們來講講定時器 定時器 標準庫中的定時器 schedule()方法 掃描線程 手動實現定時器 任務類 存儲任務的數據結構 定時器類 如果各位對文章的內容感興趣的話&#xff0c;請點點小贊&am…

【Python】列表

目錄 一、列表的概念 二、列表的創建 1.變量名 [ ] ..... 2.通過Python內置 的I ist類的構造函數來創建列表 三、操作列表元素的方法 1. 修改 2. 增加元素 3. 刪除 4. 其他操作 四、遍歷列表 五、列表排序 六、列表切片&#xff08;list slicing&#xff09; 七、…

淺談什么是計算機科學與技術(Computer Science,CS)

計算機科學的核心內容 計算機科學&#xff08;Computer Science, CS&#xff09;涵蓋了以下主要領域&#xff1a; 硬件&#xff1a;涉及數字電路、集成電路、存儲器和硬件設計與驗證方法等。 例子&#xff1a;學習如何設計和實現一個簡單的CPU&#xff0c;包括理解指令集、時鐘…

值得細讀的8個視覺大模型生成式預訓練方法

作者&#xff1a;vasgaowei&#xff08;已授權原創&#xff09; 編輯: AI生成未來 鏈接&#xff1a;https://zhuanlan.zhihu.com/p/677794719 大語言模型的進展催生出了ChatGPT這樣的應用&#xff0c;讓大家對“第四次工業革命”和“AGI”的來臨有了一些期待&#xff0c;也作為…

Linux基礎指令介紹與詳解——原理學習

前言&#xff1a;本節內容標題雖然為指令&#xff0c;但是并不只是講指令&#xff0c; 更多的是和指令相關的一些原理性的東西。 如果友友只想要查一查某個指令的用法&#xff0c; 很抱歉&#xff0c; 本節不是那種帶有字典性質的文章。但是如果友友是想要來學習的&#xff0c;…

[ALSA]從零開始,使用ALSA驅動播放一個音頻

前言 最近學了不少有關音頻相關的&#xff0c;最近搞一下ALSA驅動 安裝 參考Linux應用開發【第八章】ALSA應用開發 中提到的ALSA庫及工具章節&#xff0c;本文中有比較詳細的有關ALSA驅動引用程序怎么安裝的&#xff0c;這里不再贅述。 關于ALSA&#xff0c;就當成一個音頻…

深入淺出:npm常用命令詳解與實踐【保姆級教程】

大家好,我是CodeQi! 在我剛開始學習前端開發的時候,有一件事情讓我特別頭疼:管理和安裝各種各樣的依賴包。 那時候,我還不知道 npm 的存在,手動下載和管理這些庫簡直是噩夢。 后來,我終于接觸到了 npm(Node Package Manager),它不僅幫我解決了依賴管理問題,還讓我…

Python深度理解系列之【排序算法——冒泡排序】

讀者大大們好呀&#xff01;&#xff01;!?????? &#x1f440;期待大大的關注哦?????? &#x1f680;歡迎收看我的主頁文章??木道尋的主頁 文章目錄 &#x1f525;前言&#x1f680;冒泡排序python實現算法實現圖形化算法展示 ??????總結 &#x1f525;前…

Apache POI、EasyPoi、EasyExcel

目錄 ?編輯 &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&#xff09;EasyPoi使用 &#xff08;三&#xff09;EasyExcel使用 寫 讀 最簡單的讀? 最簡單的讀的excel示例? 最簡單的讀的對象? &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&…

golang go-bindata打包配置文件嵌入到二進制文件

go-bindata打包配置文件嵌入到二進制文件 項目中難免會用到一些靜態資源和配置文件&#xff0c;但是常規打包的二進制文件無法再其他目錄正常運行&#xff08;靜態資源和配置文件不存在&#xff09; 有類似需求的可以安裝使用&#xff1a;go-bindata進行編譯處理配置文件 go-bi…

train_encoder_decoder.py

train_encoder_decoder.py from __future__ import print_function #為了確保代碼同時兼容Python 2和Python 3版本中的print函數# 導入標準庫和第三方庫 import os.path #導入了Python的os.path模塊&#xff0c;用于處理文件和目錄路徑 from os import path #從os模塊中導入了…

【場景題】數據庫優化和接口優化——異步思想

理解 異步處理&#xff1a; 對于耗時的操作&#xff0c;可以考慮使用異步處理方式來提升接口的響應速度。用戶可以在不阻塞當前操作的情況下&#xff0c;等待異步操作的結果。 異步處理在數據庫優化中的應用 雖然數據庫操作本身&#xff08;如查詢、插入、更新等&#xff09…

Git 安裝

目錄 Git 安裝 Git 安裝 在使用 Git 前我們需要先安裝 Git。Git 目前支持 Linux/Unix、Solaris、Mac 和 Windows 平臺上運行。Git 各平臺安裝包下載地址為&#xff1a;http://git-scm.com/downloads 在 Linux 平臺上安裝&#xff08;包管理工具安裝&#xff09; 首先&#xff0…

IIS在Windows上的搭建

&#x1f4d1;打牌 &#xff1a; da pai ge的個人主頁 &#x1f324;?個人專欄 &#xff1a; da pai ge的博客專欄 ??寶劍鋒從磨礪出&#xff0c;梅花香自苦寒來 目錄 一 概念&#xff1a; 二網絡…

深入理解C++中的鎖

目錄 1.基本互斥鎖&#xff08;std::mutex&#xff09; 2.遞歸互斥鎖&#xff08;std::recursive_mutex&#xff09; 3.帶超時機制的互斥鎖&#xff08;std::timed_mutex&#xff09; 4.帶超時機制的遞歸互斥鎖&#xff08;std::recursive_timed_mutex&#xff09; 5.共享…