【聊聊原子性,中斷,以及nodejs中的具體示例】

什么是原子性

從一個例子說起, x++ ,讀和寫 ,

如圖假設多線程,線程1和線程2同時操作變量x,進行x++的操作,那么由于寫的過程中,都會先讀一份x數據到cpu的寄存器中,所以這個時候cpu1 和 cpu2 拿到了相同的變量x,假設初始x值為1,則cpu1拿到的x為1,cpu2拿到的x為1,都操作并寫回給x后,x的值為2。

預期加兩次,結果為3,但是實際由于多線程同時操作同一個變量了 ,可能產生寫覆蓋。進一步看,這其中還要再提起一個詞,中斷。

中斷

多線程 - cpu中斷

多線程下,常見一個或者多個操作在 CPU 執行時候,中斷,切出再切回。

對于多線程來說,程序在運行一段代碼的時候,可能會中途切出,這種來回切出和切回,就出現了上面x++的情況。產生了寫覆蓋的問題。

那么不用多線程,只用單線程,是不是就不會存在中斷的問題,是不是就安全了,其實也不安全。因為線程下面還有協程(如python Coroutine),或如nodejs中 event loop,其雖然不會在cpu運算的時候切出,但是會在等待io的時候切出。

單線程 - io中斷

單線程下,一個或者多個IO操作執行的過程中,中斷,切出再切回。

一個單線程切出的例子,拿nodejs中event loop舉例,worker1 和 worker2分別產生event,去累加result,但是在累加的過程中會await sleep 模擬等待io,這會導致由于等待io而引起的中斷,切出。

非原子性示例

function sleep(ms: number) {return new Promise(resolve => setTimeout(resolve, ms));
}let result = 0;async function worker1() {let maxtime1 = 1;while(maxtime1 <= 100) {let name = 'worker1';// 執行100次)console.log(`${name} calculate current time ${maxtime1}`)// 開始工作let resultCopy = result;// 讓出await sleep(10);resultCopy += 1;result = resultCopy;maxtime1 += 1;}
}async function worker2() {let maxtime2 = 1;while(maxtime2 <= 100) {let name = 'worker2';// 執行100次console.log(`${name} calculate current time ${maxtime2}`)// 開始工作let resultCopy = result;// 讓出await sleep(10);resultCopy += 1;result = resultCopy;maxtime2 += 1;}
}(async () => {console.log('start calculate')const startTime = Date.now();Promise.all([worker1(), worker2()]).then(() => {const endTime = Date.now();// 預期是200 ,但是由于會寫覆蓋,所以最終小于200.console.log(`耗時: ${endTime - startTime}ms`);console.log('result:', result);}).catch((error) => {console.error('A worker failed with error:', error);});
})()

運行結果,通過結果 ,甚至輸出結果直接就是100,因為worker1 和 worker2的并行執行,導致每次累加計算前,worker1 和 worker2 都拿到相同的值

那么如何避免這種情況,讓worker1的代碼片段執行完,再執行的worker2的代碼片段,不切出,達到原子性,一種方法就是加鎖,下面繼續看如何加鎖達到原子性,

原子性示例

通過加鎖,可以實現代碼片段的原子性 ,如下

import { Mutex } from 'async-mutex';
const mutex = new Mutex();function sleep(ms: number) {return new Promise(resolve => setTimeout(resolve, ms));
}let result = 0;async function worker1() {let maxtime1 = 1;// 執行100次while(maxtime1 <= 100) {let name = 'worker1';// 開始工作// 鎖住,const release = await mutex.acquire();console.log(`${name} calculate current time ${maxtime1}, before start calulate result: ${result}`)// rlet resultCopy = result;// 讓出cpu,這里即使讓出,其它worker由于無法獲取鎖,所以會一直等待await sleep(10);resultCopy += 1;// w result = resultCopy;console.log(`${name} calculate current time ${maxtime1}, after calulate result: ${result}`)release();maxtime1 += 1;}
}async function worker2() {let maxtime2 = 1;// 執行100次while(maxtime2 <= 100) {let name = 'worker2';// 開始工作// 鎖住,const release = await mutex.acquire();console.log(`${name} calculate current time ${maxtime2}, before start calulate result: ${result}`)// rlet resultCopy = result;// 讓出cpuawait sleep(10);resultCopy += 1;// w result = resultCopy;console.log(`${name} calculate current time ${maxtime2}, after calulate result: ${result}`)release();maxtime2 += 1;}
}(async () => {console.log('start calculate')const startTime = Date.now();Promise.all([worker1(), worker2()]).then(() => {const endTime = Date.now();// 預期是200 ,但是由于會寫覆蓋,所以最終小于200.console.log(`耗時: ${endTime - startTime}ms`);console.log('result:', result);}).catch((error) => {console.error('A worker failed with error:', error);});
})()

此時,在看輸出結果,可以發現由于有鎖,worker1 和 worker2是串行累加的,不會在執行累加的過程中切出,所以最終累加的結果是200,符合預期。

同時可以發現,由于加鎖,整體串行,會導致整體運行時間增加。這里就不得不多提下,Event Loop 是一種異步編程模型,io切出本身屬于提高效率的設計,所以如果不是需要原子性,不是同時操作同一個變量,則沒必要加鎖降低效率。

結語

總結 ,對于編程中的原子性,如果說一段代碼是原子性的,則這段代碼無論是cpu 還是 io等待 都不能被切出。這段代碼需要完整的執行,這才是我們預期的一段代碼的原子性。

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

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

相關文章

MyBatis-plus(下)

目錄 靜態工具 邏輯刪除 枚舉處理器 ?編輯?編輯JSON處理器 分頁插件 案例 靜態工具 只有save與update不需要傳class字節碼 UserController: MyServiceImpl: 改造根據id批量查詢用戶的接口&#xff0c;查詢用戶的同時&#xff0c;查詢出用戶對應的所有地址 Overrid…

容器內存

一、容器內存概述 容器本質上還是一個進程&#xff0c;是一個被隔離和限制的進程。因此容器內存和進程內存在表現形式上其實是一樣的&#xff0c;這塊主要涉及三部分內容&#xff1a;RSS&#xff0c;page cache和swap這三部分&#xff0c;容器基于memory Cgroup對內存進行限制…

用國內鏡像安裝docker 和 docker-compose (ubuntu)

替代方案&#xff0c;改用國內的鏡像站(網易鏡像&#xff09; 1.清除舊版本&#xff08;可選操作&#xff09; for pkg in docker.io docker-doc docker-compose podman-docker containerd runc; do apt-get remove $pkg; done 2.安裝docker apt-get update 首先安裝依賴 apt-g…

Linux驅動開發實戰寶典:設備模型、模塊編程、I2C/SPI/USB外設精講

摘要: 本文將帶你走進 Linux 驅動開發的世界,從設備驅動模型、內核模塊開發基礎開始,逐步深入 I2C、SPI、USB 等常用外設的驅動編寫,結合實際案例,助你掌握 Linux 驅動開發技能。 關鍵詞: Linux 驅動,設備驅動模型,內核模塊,I2C,SPI,USB 一、Linux 設備驅動模型 Li…

mysql創建表的規范

名稱 建表的時候&#xff0c;給表&#xff0c;字段和索引起個好名字 見名知意&#xff1a;好的名字能夠降低溝通和維護的成本名字不宜過長&#xff0c;盡量控制在30個字符以內 大小寫 名字盡量都用小寫字母&#xff0c;因為從視覺上&#xff0c;小寫字母更容易讓人讀懂全部大寫…

Linux嵌入式中MQTT的使用

MQTT是什么&#xff1f; MQTT&#xff08;Message Queuing Telemetry Transport&#xff0c;消息隊列遙測傳輸協議&#xff09;&#xff0c;是一種基于發布/訂閱&#xff08;Publish/Subscribe&#xff09;模式的輕量級通訊協議&#xff0c;該協議構建于TCP/IP協議上&#xff0…

駕馭npm更新之力:深入掌握npm update命令的精髓

駕馭npm更新之力&#xff1a;深入掌握npm update命令的精髓 在JavaScript和Node.js的世界中&#xff0c;npm&#xff08;Node Package Manager&#xff09;作為默認的包管理器&#xff0c;扮演著至關重要的角色。它不僅用于安裝和管理項目依賴&#xff0c;還提供了更新這些依賴…

SpringBoot3.3集成knif4j-swagger文檔方式和使用案例

springboot3 集成 knif4j &#xff1a; 訪問地址&#xff1a; swagger 接口文檔默認地址&#xff1a;http://localhost:8080/swagger-ui.html# Knife4j 接口文檔默認地址&#xff1a;http://127.0.0.1:8080/doc.html Maven: <dependency><groupId>com.github.x…

2024 COMMUNITY DAY User Group 社區嘉年華 云計算與 AI 技術交融盛會共筑多元智慧未來

亞馬遜云科技User Group&#xff0c;深圳 Community Day 活動流程搶先知道&#xff01; ? 7月7日 &#x1f3e0; 深圳南山區香港中文大學 &#x1f4e3;主論壇國際大咖云集&#xff0c;共襄科技盛宴&#xff01; &#x1f389;三大主題論壇&#xff1a;人工智能、大數據、動…

MyBatis系列三: XxxMapper.xml-SQL映射文件

XxxMapper.xml-SQL映射文件 官方文檔基本介紹詳細說明基本使用parameterType(輸入參數類型)傳入HashMapresultMap(結果集映射) 官方文檔 文檔地址: https://mybatis.org/mybatis-3/zh_CN/sqlmap-xml.html 基本介紹 1.MyBatis的真正強大在于它的語句映射(在XxxMapper.xml配置…

2024年06月CCF-GESP編程能力等級認證Python編程一級真題解析

本文收錄于專欄《Python等級認證CCF-GESP真題解析》,專欄總目錄:點這里,訂閱后可閱讀專欄內所有文章。 一、單選題(每題 2 分,共 30 分) 第 1 題 小楊父母帶他到某培訓機構給他報名參加CCF組織的GESP認證考試的第1級,那他可以選擇的認證語言有幾種?( ) A. 1 B. 2 C…

React@16.x(45)路由v5.x(10)源碼(2)- history

目錄 1&#xff0c;作用1.1&#xff0c;createBrowserHistory1.2&#xff0c;createHashHistory1.3&#xff0c;createMemoryHistory 2&#xff0c;history 對象的屬性2.1&#xff0c;action2.2&#xff0c;push / replace / go / goBack / goForward2.3&#xff0c;location2.…

網絡配線架的隱藏功能

網絡布線是確保現代信息社會高效運轉的關鍵技術之一。在這一領域&#xff0c;網絡配線架扮演著至關重要 的角色。它不僅僅是一個簡單的物理連接點&#xff0c;更擁有許多隱藏功能&#xff0c;這些功能極大地提升了網絡的 效率、穩定性和可管理性。 1、集中管理 網絡配線架提…

【BES2500x系列 -- RTX5操作系統】深入探索CMSIS-RTOS RTX -- 同步與通信篇 -- 消息隊列和郵箱處理 --(四)

&#x1f48c; 所屬專欄&#xff1a;【BES2500x系列】 &#x1f600; 作??者&#xff1a;我是夜闌的狗&#x1f436; &#x1f680; 個人簡介&#xff1a;一個正在努力學技術的CV工程師&#xff0c;專注基礎和實戰分享 &#xff0c;歡迎咨詢&#xff01; &#x1f49…

經典FC游戲web模擬器--EmulatorJS

簡介 EmulatorJS是一個基于JavaScript和Webassembly技術的虛擬環境的實現&#xff0c;可以在網頁中運行各種經典FC游戲系統&#xff0c;支持任天堂、世嘉、雅達利等經典紅白機。EmulatorJS的誕生使得諸如超級瑪麗、坦克大戰、魂斗羅等經典FC游戲能夠以一種全新的方式回歸。本文…

SAP MM模塊的ATP檢查

前面幾篇文章都演示和說明ATP的一些設置和操作&#xff0c;通常情況下ATP的檢查PP模塊&#xff0c;SD模塊用的相對來說是比較多的&#xff0c;但是實際上MM模塊也會遵循ATP的可用性的檢查規則。 當我們在做311、301等移動類型時&#xff0c;系統會根據相應的可用性檢查規則&am…

Linux常用指令匯總

Linux常用指令匯總 Cfilt 功能&#xff1a;解析C程序中被修飾的符號&#xff0c;比如變量與函數名稱。 示例&#xff1a; 解析編譯器 g 修飾的函數名稱。 cfilt -s gnu-v3 _Z5printRKSs print(std::basic_string<char, std::char_traits<char>, std::allocator<…

Django 多對多關系

多對多關系作用 Django 中&#xff0c;多對多關系模型的作用主要是為了表示兩個模型之間的多對多關系。具體來說&#xff0c;多對多關系允許一個模型的實例與另一個模型的多個實例相關聯&#xff0c;反之亦然。這在很多實際應用場景中非常有用&#xff0c;比如&#xff1a; 博…

【每日一個Git命令: cherry-pick】

git cherry-pick 命令的作用是將指定的提交&#xff08;commit&#xff09;應用到其他分支上。這個命令允許你選擇一個或多個已有的提交&#xff0c;并將它們作為新的提交引入到當前分支中。 這個過程不會改變項目的歷史記錄&#xff0c;因為它實際上是創建了這些提交的副本。…

BMA530 運動傳感器

型號簡介 BMA530是博世&#xff08;bosch-sensortec&#xff09;的一款運動傳感器。時尚簡約的可穿戴設備為功能強大的組件提供了很小的空間。具有先進功能集的下一代加速度計是世界上最小的加速度傳感器&#xff08;1.2 x 0.8 x 0.55 mm&#xff09;。它專為緊湊型設備而設計&…