溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

線程安全是什么

發(fā)布時間:2021-06-29 16:18:22 來源:億速云 閱讀:170 作者:Leah 欄目:編程語言

今天就跟大家聊聊有關(guān)線程安全是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結(jié)了以下內(nèi)容,希望大家根據(jù)這篇文章可以有所收獲。

內(nèi)容大綱

線程安全是什么

小故事

帶薪蹲坑,相信都是大伙都愛做的事情,阿星也不例外,但是我司所在的樓層的坑位較少,粥少僧多,十分煩惱。

阿星(線程A)每次去廁所(共享資源),門都是鎖著的,說明有同事在里面占著坑(線程B持有鎖),只能無奈的在外面乖乖的等著,不久后沖水聲響起,同事爽完出來(線程B釋放鎖),阿星一個健步進(jìn)入廁所把門鎖?。ň€程A持有鎖),享受屬于自己的空間,晚來的其他同事只能乖乖排隊(duì),一切都是那么井然有序。

假設(shè)門鎖壞了,井然有序就不存在了,上廁所不再是享受,而是高度緊張,防止門突然被打開,更糟糕的是,開門時,是個妹子,這下不僅僅是線程安全問題,還有數(shù)組越界了。

故事說完,扯了那么多,就是想說明,在多線程環(huán)境里,對共享資源進(jìn)行操作,如果多線程之間不做合理的協(xié)作(互斥與同步),那么一定會發(fā)生翻車現(xiàn)場。

競爭條件

因?yàn)槎嗑€程共享進(jìn)程資源,在操作系統(tǒng)調(diào)度進(jìn)程內(nèi)的多線程時,必然會出現(xiàn)多線程競爭共享資源問題,如果不采取有效的措施,則會造成共享資源的混亂!

線程安全是什么

來寫個小例子,創(chuàng)建兩個線程,它們分別對共享變量 i 自增 1 執(zhí)行 1000 次,如下代碼

線程安全是什么

正常來說,i 變量最后的值是 2000 ,可是并非如此,我們執(zhí)行下代碼看看結(jié)果

  • 結(jié)果:2000

  • 結(jié)果:1855

運(yùn)行了兩次,結(jié)果分別是1855、2000,我們發(fā)現(xiàn)每次運(yùn)行的結(jié)果不同,這在計(jì)算機(jī)里是不能容忍的,雖然是小概率出現(xiàn)的錯誤,但是小概率它一定是會發(fā)生的。

匯編指令

為了搞明白到底發(fā)生了什么事情,我們必須要了解匯編指令執(zhí)行,以 i1 為例子,匯編指令的執(zhí)行過程如下

線程安全是什么

好家伙,一個加法動作,在 C P U 運(yùn)行,實(shí)際要執(zhí)行 3 條指令。

現(xiàn)在模擬下線程A與線程B的運(yùn)行,假設(shè)此時內(nèi)存變量 i 的值是 0,線程A加載內(nèi)存的 i 值到寄存器,對寄存器 i 值加 1,此時 i 值是 1,正準(zhǔn)備執(zhí)行下一步寄存器 i 值回寫內(nèi)存,時間片使用完了,發(fā)生線程上下文切換,保存線程的私有信息到線程控制塊T C P。

操作系統(tǒng)調(diào)度線程B執(zhí)行,此時的內(nèi)存變量 i 依然還是 0,線程B執(zhí)行與線程A一樣的步驟,它很幸運(yùn),在時間片使用完前,執(zhí)行完了加 1,最終回寫內(nèi)存,內(nèi)存變量 i 值是 1。

線程B時間片使用完后,發(fā)生線程上下文切換,回到線程A上次的狀態(tài)繼續(xù)執(zhí)行,寄存器中的 i 值回寫內(nèi)存,內(nèi)存變量再次被設(shè)置成 1。

按理說,最后的 i 值應(yīng)該是 2,但是由于不可控的調(diào)度,導(dǎo)致最后 i 值是 1,下面是線程A與線程B的流程圖

線程安全是什么

  • 第一步:內(nèi)存取出 i 值,加載進(jìn)寄存器

  • 第二步:對寄存器內(nèi)的 i 值加 1

  • 第三步:寄存器內(nèi)的 i 值取出 加載進(jìn)內(nèi)存

小結(jié)

這種情況稱為競爭條件(race condition),多線程相互競爭操作共享資源時,由于運(yùn)氣不好,在執(zhí)行過程中發(fā)生線程上下文切換,最后得到錯誤的結(jié)果,事實(shí)上,每次運(yùn)行都可能得到不同的結(jié)果,因此輸出的結(jié)果存在不確定性(indeterminate)。

互斥與同步

為了解決因競爭條件出現(xiàn)的線程安全,操作系統(tǒng)是通過互斥與同步來解決此類問題。

互斥概念

多線程執(zhí)行共享變量的這段代碼可能會導(dǎo)致競爭狀態(tài),因此我們將此段代碼稱為臨界區(qū)(critical section),它是執(zhí)行共享資源的代碼片段,一定不能給多線程同時執(zhí)行。

所以我們希望這段代碼是互斥(mutualexclusion)的,也就說執(zhí)行臨界區(qū)(critical section)代碼段的只能有一個線程,其他線程阻塞等待,達(dá)到排隊(duì)效果。

線程安全是什么

互斥并不只是針對多線程的競爭條件,同時還可用于多進(jìn)程,避免共享資源混亂。

同步概念

互斥解決了「多進(jìn)程/線程」對臨界區(qū)使用的問題,但是它沒有解決「多進(jìn)程/線程」協(xié)同工作的問題

我們都知道在多線程里,每個線程一定是順序執(zhí)行的,它們各自獨(dú)立,以不可預(yù)知的速度向前推進(jìn),但有時候我們希望多個線程能密切合作,以實(shí)現(xiàn)一個共同的任務(wù)。

所謂同步,就是「多進(jìn)程/線程間」在一些關(guān)鍵點(diǎn)上可能需要互相等待與互通消息,這種相互制約的等待與互通信息稱為「進(jìn)程/線程」同步。

舉個例,有兩個角色分別是研發(fā)、質(zhì)量管控,質(zhì)量管控測試功能,需要等研「發(fā)完成開發(fā)」,研發(fā)要修bug也要等質(zhì)量管控「測試完成提交B U G」,正常流程是研發(fā)完成開發(fā),通知質(zhì)量管控進(jìn)行測試,質(zhì)量管控測試完成,通知研發(fā)人員修復(fù)bug。

線程安全是什么

互斥與同步的區(qū)別

  • 互斥:某一資源同時只允許一個訪問者對其進(jìn)行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的(操作 A 和操作 B 不能在同一時刻執(zhí)行)

  • 同步:互斥的基礎(chǔ)上,通過其它機(jī)制實(shí)現(xiàn)訪問者對資源的有序訪問。在大多數(shù)情況下,同步已經(jīng)實(shí)現(xiàn)了互斥(操作 A 應(yīng)在操作 B 之前執(zhí)行,操作 C 必須在操作 A 和操作 B 都完成之后才能執(zhí)行)

顯然,同步是一種更為復(fù)雜的互斥,而互斥是一種特殊的同步。也就是說互斥是兩個線程之間不可以同時運(yùn)行,他們會相互排斥,必須等待一個線程運(yùn)行完畢,另一個才能運(yùn)行,而同步也是不能同時運(yùn)行,但他是必須要按照某種次序來運(yùn)行相應(yīng)的線程(也是一種互斥)! 

互斥與同步的實(shí)現(xiàn)

互斥與同步可以保證「多進(jìn)程/線程間正確協(xié)作」 ,但是互斥與同步僅僅只是概念,操作系統(tǒng)必須要提供對應(yīng)的實(shí)現(xiàn),針對互斥與同步的實(shí)現(xiàn)有下面兩種

  • 鎖:加鎖、解鎖操作(互斥)

  • 信號量:P、V 操作(同步)

這兩個種方式都可以實(shí)現(xiàn)「多進(jìn)程/線程」互斥,信號量比鎖的功能更強(qiáng)一些,它還可以方便地實(shí)現(xiàn)「多進(jìn)程/線程」同步。

顧名思義,給臨界區(qū)上一把鎖,任何進(jìn)入臨界區(qū))的線程,必須先執(zhí)行加鎖操作,加鎖成功,才能進(jìn)入臨界區(qū),在離開臨界區(qū)時再釋放鎖,達(dá)到互斥的效果。

線程安全是什么

鎖的實(shí)現(xiàn)方式又分為「忙等待鎖」和「無忙等待鎖」

忙等鎖

檢查并設(shè)置(test-and-set-lock,TSL)是一種不可中斷的原子運(yùn)算,它屬于原子操作指令,可以通過它來實(shí)現(xiàn)忙等鎖(自旋鎖)。

test-and-set-lock 指令偽代碼

線程安全是什么

檢查并設(shè)置做了如下幾個步驟

  • 檢查舊值是否相等

  • 相等設(shè)置新值,返回原舊值(成功)

  • 不相等,無任何操作,直接返回原舊值(失?。?/p>

上面的步驟,把它看成一步并具備原子性,原子性的意思是指全部執(zhí)行或都不執(zhí)行,不會出現(xiàn)執(zhí)行到一半的中間狀態(tài).

偽代碼testAndSetLock實(shí)現(xiàn)忙等鎖(自旋鎖)

線程安全是什么

下面兩種場景運(yùn)行

  • 單線程:假設(shè)一個線程訪問臨界區(qū),執(zhí)行 getLock 方法,檢查舊值 0 通過,更新原舊值 0 為新值 1,返回原舊值 0,獲取鎖成功,離開臨界區(qū)時,執(zhí)行 unLock 方法,檢查舊值 1 通過,更新原舊值 1 為新值 0,釋放鎖成功。

  • 多線程:假設(shè)兩個線程,線程A訪問臨界區(qū),執(zhí)行 getLock 方法,檢查舊值 0 通過,更新原舊值 0 為新值 1,返回原舊值 0,獲取鎖成功,此時線程B執(zhí)行 getLock 方法,舊值檢查失敗,獲取鎖失敗,一直循環(huán)直到更新成功為止,當(dāng)線程A離開臨界區(qū)時,執(zhí)行 unLock 方法,檢查舊值 1 通過,更新原舊值 1 為新值 0,釋放鎖成功,線程B獲取鎖成功。

當(dāng)獲取不到鎖時,線程就會一直 wile 循環(huán),不做任何事情,所以就被稱為忙等待鎖,也被稱為自旋鎖。

這是最簡單的鎖,一直自旋,利用 C P U 周期,直到鎖可用。在單處理器上,需要搶占式的調(diào)度器(即不斷通過時鐘中斷一個線程,運(yùn)行其他線程)。否則,自旋鎖在 C P U 上無法使用,因?yàn)橐粋€自旋的線程永遠(yuǎn)不會放棄 C P U。

無忙等鎖

顧名思義,無忙等鎖不需要主動自旋,被動等待喚醒即可,在沒有獲取到鎖的時候,就把該線程加入到等待隊(duì)列,讓出 C P U 給其他線程,其他線程釋放鎖時,再從等待隊(duì)列喚醒該線程。

線程安全是什么

兩種鎖的實(shí)現(xiàn)都是基于檢查并設(shè)置(test-and-set-lock,TSL),上面只是簡單的偽代碼,實(shí)際上操作系統(tǒng)的實(shí)現(xiàn)會更復(fù)雜,但是基本思想與大致流程還是與本例一樣。

信號量

操作系統(tǒng)中協(xié)調(diào)「多線程/進(jìn)程」共同配合工作,就是通過信號量實(shí)現(xiàn)的,通常信號量代表「資源數(shù)量」,對應(yīng)一個整型(s e n)變量,還有兩個原子操作的系統(tǒng)調(diào)用函數(shù)來控制「資源數(shù)量」。

  • P 操作:將 s e n1,相減后,如果 s e n < 0 ,則進(jìn)程/線程進(jìn)入阻塞等待,否則繼續(xù),P 操作可能會阻塞

  • V 操作:將 s e n1 ,相加后,如果 s e n <= 0,喚醒等待中的進(jìn)程/線程,V 操作不會阻塞

P V操作必須是成對出現(xiàn),但是沒有順序要求,也就說你可以P V或V P。

舉個例子,最近新冠病毒又出來搗亂了,為了自身安全,大家都去打疫苗,因?yàn)獒t(yī)生只有兩位(相當(dāng)于2個資源的信號量),所以同時只能為兩個人接種疫苗,過程如下圖

線程安全是什么

  • 信號量等于 0 時,代表無資源可用

  • 信號量小于 0 時,代表有線程在阻塞

  • 信號量大于 0 時,代表資源可用

使用偽代碼實(shí)現(xiàn)P V 信號量

線程安全是什么

P V操作的函數(shù)是由操作系統(tǒng)管理和實(shí)現(xiàn)的,所以 P V 函數(shù)是具有原子性的。

實(shí)踐

信號量還是比較有意思的,這里來做幾個實(shí)踐,加深大家對信號量的理解,實(shí)踐的內(nèi)容分別是

  • 信號量實(shí)現(xiàn)互斥

  • 信號量實(shí)現(xiàn)事件同步

  • 信號量實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者

互斥

使用信號量實(shí)現(xiàn)互斥非常簡單,信號量數(shù)量為1,線程進(jìn)入臨界區(qū)進(jìn)行 P 操作,離開臨界區(qū)進(jìn)行 V 操作。

線程安全是什么

事件同步

以前面說的研發(fā)、質(zhì)量管控線程為例子,實(shí)現(xiàn)事件同步的效果,偽代碼如下

線程安全是什么

首先抽象出兩個信號量,「是否能提測」與「是否能修BUG」,它們默認(rèn)都是否,也就是 0,關(guān)鍵點(diǎn)就是對兩個信號量進(jìn)行 P V 操作

  • 質(zhì)量管控線程詢問開發(fā)線程有沒有完成開發(fā),執(zhí)行 P 操作 p(this.rDSemaphore)

    • 如果沒有完成開發(fā),this.rDSemaphore1 結(jié)果為 -1,質(zhì)量管控線程阻塞等待喚醒(等后續(xù)研發(fā)線程進(jìn)行 V 操作)

    • 如果完成開發(fā),說明研發(fā)線程先執(zhí)行 V 操作 v(this.rDSemaphore) 完成開發(fā),this.rDSemaphore1 結(jié)果 1,此時質(zhì)量管控線程 P 操作 this.rDSemaphore1 結(jié)果 0,進(jìn)行后面的提測工作

  • 研發(fā)線程詢問質(zhì)量管控線程能不能修復(fù)B U G,執(zhí)行 P 操作 p(this.qualitySemaphore)

    • 如果不可以修復(fù)B U G,this.qualitySemaphore1 結(jié)果為 -1,研發(fā)線程阻塞等待喚醒(等后續(xù)質(zhì)量管控線程執(zhí)行 V 操作)

    • 如果可以修復(fù)B U G,說明質(zhì)量管控線程先執(zhí)行 V 操作 v(this.qualitySemaphore) 提交BUG, this.qualitySemaphore1 結(jié)果為 1,此時研發(fā)線程 P 操作 this.qualitySemaphore1 結(jié)果 0,進(jìn)行后面的修復(fù) B U G 操作

  • 流程

    • 質(zhì)量管控線程執(zhí)行 P 操作 p(this.rDSemaphore) 能不能提測,this.rDSemaphore1 結(jié)果是 -1 ,不能進(jìn)行提測,質(zhì)量管控線程阻塞等待喚醒

    • 研發(fā)線程運(yùn)行,執(zhí)行 V 操作 v(this.rDSemaphore) 完成研發(fā)功能,this.rDSemaphore1 結(jié)果是 0,通知質(zhì)量管控線程提測

    • 研發(fā)線程繼續(xù)執(zhí)行 P 操作 p(this.qualitySemaphore) 能不能修復(fù)B U G,this.qualitySemaphor1 結(jié)果是 -1,不能修復(fù)B U G,研發(fā)線程阻塞等待喚醒

    • 質(zhì)量管控線程喚醒后進(jìn)行提測,提測完畢執(zhí)行 V 操作 v(this.qualitySemaphore) 完成提測與提交相關(guān)B U G,this.qualitySemaphore1 結(jié)果是 0,通知研發(fā)線程進(jìn)行B U G修復(fù)

生產(chǎn)者與消費(fèi)者

生產(chǎn)者與消費(fèi)者是一個比較經(jīng)典的線程同步問題,我們先分析下有那些角色

  • 生產(chǎn)者:生產(chǎn)事件放入緩沖區(qū)

  • 消費(fèi)者:從緩沖區(qū)消費(fèi)事件

  • 緩沖區(qū):裝載事件的容器

線程安全是什么

問題分析可以得出:

  • 任何時刻只能有一個線程操作緩沖區(qū),說明操作緩沖區(qū)是臨界代碼,需要互斥

  • 緩沖區(qū)空時,消費(fèi)者必須等待生產(chǎn)者生成數(shù)據(jù)

  • 緩沖區(qū)滿時,生產(chǎn)者必須等待消費(fèi)者取出數(shù)據(jù)

通過問題分析我們可以抽象出3個信號量

  • 互斥信號量:互斥訪問緩沖區(qū),初始化 1

  • 消費(fèi)者資源信號量:緩沖區(qū)是否有事件,初始化 0,無事件

  • 生產(chǎn)者信號量:緩沖區(qū)是否有空位裝載事件,初始化 N (緩沖區(qū)大?。?/p>

偽代碼如下

線程安全是什么

關(guān)鍵的 P V 操作如下

  • 生產(chǎn)線程,在往緩沖區(qū)裝載事件之前,執(zhí)行 P 操作 p(this.produceSemaphore) ,緩沖區(qū)空槽數(shù)量減 1,結(jié)果 < 0 說明無空槽,阻塞等待「消費(fèi)線程」喚醒,否則執(zhí)行后續(xù)邏輯

  • 不論是生產(chǎn)線程還是消費(fèi)線程在操作緩沖區(qū)都要執(zhí)行 P V 臨界區(qū)操作 p(this.mutexSemaphore)v(this.mutexSemaphore),這里就不做過多概述了

  • 消費(fèi)線程,在從緩存區(qū)消費(fèi)事件之前,執(zhí)行 P 操作 p(this.consumeSemaphore),緩沖區(qū)事件數(shù)量減 1,結(jié)果 < 0 說明緩沖區(qū)無事件消費(fèi),阻塞等待「生產(chǎn)線程」喚醒,否執(zhí)行后續(xù)邏輯

  • 生產(chǎn)線程與消費(fèi)線程,執(zhí)行完「裝載/消費(fèi)」后,都要喚醒對應(yīng)的「生產(chǎn)/消費(fèi)線程」,執(zhí)行 V 操作「緩沖區(qū)空槽加 1/緩沖區(qū)事件加 1

看完上述內(nèi)容,你們對線程安全是什么有進(jìn)一步的了解嗎?如果還想了解更多知識或者相關(guān)內(nèi)容,請關(guān)注億速云行業(yè)資訊頻道,感謝大家的支持。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI