多線程鎖原理

多線程鎖原理

  • 臨界區(qū): 在臨界區(qū)內(nèi),會(huì)對(duì)共享資源進(jìn)行訪問和修改
  • 共享資源: 在同一時(shí)間只能被單個(gè)線程所持有

訪問臨界區(qū)過程:

  1. 申請(qǐng)臨界區(qū)權(quán)限
  2. 訪問臨界區(qū)
  3. 歸還權(quán)限, 退出臨界區(qū)

線程安全問題:
12306賣票問題,既不能多賣又不能少賣

if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 

如果多個(gè)線程,同時(shí)操作的話,可能會(huì)造成多賣票,本來只有1000張票,卻賣個(gè)1001個(gè)人

為了避免這種情況,可以嘗試使用標(biāo)志位來解決

var isSaling = false // 正在售票標(biāo)志位

while isSaling { // 如果有人售票,則等待
}

isSaling = true // 表示我正在售票,你們先等等
if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
isSaling =false // 賣完票了,你們可以操作了 

遺憾的是這種方式,仍然無法解決安全問題,關(guān)鍵在于查詢操作和修改操作并非不可中斷,造成多個(gè)程序進(jìn)入臨界區(qū):

Thread 1 Thread 2
query isSaling = false query isSaling = false
set isSaling = true set isSaling = true
賣票 賣票

為了防止這種情況發(fā)生。于是決定設(shè)置兩個(gè)標(biāo)志位


var isSaling1 = false // 正在售票標(biāo)志位
var isSaling2 = false // 正在售票標(biāo)志位
// =====================
// thread 1
while isSaling2 { // 2號(hào)線程正在售票,稍微等等
}

isSaling1 = true // 表示我正在售票,你們先等等
if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
isSaling1 = false // 賣完票了,你們可以操作了 

// thread 2
// =================================

while isSaling1 { // 1號(hào)線程正在售票,稍微等等
}

isSaling2 = true // 表示我正在售票,你們先等等
if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
isSaling2 = false // 賣完票了,你們可以操作了 


這種思維方式就像,有一條公家的小板凳,我先看下隔壁有沒有占用那條小板凳,沒有的話我就拿過來坐坐

但是仍然會(huì)發(fā)生多個(gè)線程進(jìn)入臨界區(qū)的問題: ** isSaling1 = false, isSaling2 = false, thread1 exec to while isSaling2 expr and thread2 exec to while isSaling1 expr **

可見光增加flag無法解決問題,換一種方式增加臨界區(qū)大小


var isSaling1 = false // 正在售票標(biāo)志位
var isSaling2 = false // 正在售票標(biāo)志位
// =====================
// thread 1
isSaling1 = true // 表示我正在售票,你們先等等
while isSaling2 { // 2號(hào)線程正在售票,稍微等等
}


if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
isSaling1 = false // 賣完票了,你們可以操作了 

// thread 2
// =================================
isSaling2 = true // 表示我正在售票,你們先等等
while isSaling1 { // 1號(hào)線程正在售票,稍微等等
}


if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
isSaling2 = false // 賣完票了,你們可以操作了 


這種方式卻會(huì)造成死鎖,死鎖的時(shí)機(jī)和之前一樣。** isSaling1 = false, isSaling2 = false, thread1 exec to while isSaling2 expr and thread2 exec to while isSaling1 expr **, 只不過一個(gè)是造成多個(gè)線程共同訪問臨界區(qū),一個(gè)是造成死鎖

為了解決這個(gè)問題,產(chǎn)生了軟件上的解法和硬件上的解法,
硬件的解法即源語。

軟件上:
這兩種算法思想其實(shí)是一致的,就是當(dāng)線程進(jìn)入臨界區(qū)之后,發(fā)現(xiàn)別的線程也進(jìn)入了臨界區(qū),怎么辦?如何避免死鎖或者并非, 這個(gè)時(shí)候就需要制定一個(gè)規(guī)則讓他們能夠正確運(yùn)行
** Dekker 算法


var isSaling1 = false // 正在售票標(biāo)志位
var isSaling2 = false // 正在售票標(biāo)志位
var turn = 1
// =====================
// thread 1
isSaling1 = true // 表示我正在售票,你們先等等
while isSaling2 { // 這個(gè)時(shí)候發(fā)現(xiàn)2號(hào)正在售票
   if turn == 2 {
        isSaling1 = fasle // 我不賣了,讓2號(hào)先賣
        while(isSaling2) {} // 等待2號(hào)賣票
        isSaling1 = true // 2號(hào)已經(jīng)賣完票了輪到我賣了 
   }
}


if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
isSaling1 = false // 賣完票了,你們可以操作了 
turn = 2
// thread 2
// =================================
isSaling2 = true // 表示我正在售票,你們先等等
while isSaling1 { // 1號(hào)線程正在售票,稍微等等
   if turn == 1 {
        isSaling2 = fasle 
        while(isSaling1) {} 
        isSaling2 = true 
   }
}


if more_ticket > 0 {
 // 賣票 
 more_ticket -= 1
} 
turn = 1
isSaling2 = false // 賣完票了,你們可以操作了 

這里turn = 1時(shí),發(fā)生碰撞1號(hào)優(yōu)先賣票,等于2時(shí)則相反,雙方不斷的切換優(yōu)先級(jí),防止死鎖的同時(shí)避免多線程并發(fā)進(jìn)入臨界區(qū)

** Peterson 算法


 var inside:array[1..2] of boolean;
           turn:integer;
    turn := 1 or 2;
    inside[1] := false;
    inside[2] := false;
cobegin
process P1
begin
/* P1 不在其臨界區(qū)內(nèi)*/ /* P2 不在其臨界區(qū)內(nèi)*/
           inside[1]:= true;
           turn := 2;
           while (inside[2] and turn=2)
do begin end;
             臨界區(qū);
           inside[1] := false;
       end;
       process P2
       begin
           inside[2] := true;
           turn := 1;
           while (inside[1] and turn=1)
do begin end; 
             臨界區(qū);
           inside[2] := false;
       end;
coend.

這個(gè)算法的規(guī)則更加簡(jiǎn)化,由于兩個(gè)線程都會(huì)修改turn的值,那么最后只有一個(gè)線程執(zhí)行,另外一個(gè)堵塞,畫個(gè)表看下會(huì)更加清晰

Thread 1 Thread 2
inside[1]:= true inside[2] := true
turn := 2
if inside[2]
if turn := 2
// 線程1 被堵塞
turn := 1
// 線程1 執(zhí)行 while (inside[1] and turn=1) 條件為真,線程被堵塞
inside[1] := false
線程2 執(zhí)行

這個(gè)算法還是很精髓的

硬件指令

硬件指令相對(duì)于軟件算法的不同在于不可分割性,也就是不會(huì)被中斷,操作一起呵成

Test And Set 指令

TS(x): 若x=true,則x:=false;return true;否則return false;

s : boolean;
s := true;
process Pi /* i = 1,2,...,n*/
           pi : boolean;
       begin
repeat pi := TS(s) until pi; /*上鎖*/ 臨界區(qū);
s := true; /*開鎖*/
end;

關(guān)鍵句子: pi := TS(s)

// s = true
// =============
p0 = TS(s) 
// 執(zhí)行之后
// s = false , p0 = true ,線程不會(huì)被阻塞

// ==========
// 由于TS指令的特殊性,導(dǎo)致執(zhí)行的時(shí)候必然會(huì)有先后順序,哪怕兩個(gè)線程是同時(shí)調(diào)用的該指令,這里假定p0線程在前,p1在后
// 此時(shí)  s = false
p1 = TS(s) 
//  執(zhí)行之后
//  p1 = false , s = false , set 失敗, 線程被阻塞,直到p0 設(shè)置 s = true

Swap 指令

  • Swap (a,b): temp:=a; a:=b; b:=temp;
lock : boolean;
lock := false;
process Pi
    pi : boolean;
begin
pi := true;
/* i = 1,2,...,n*/

repeat Swap(lock, pi) until pi = false;/*上鎖*/ 臨界區(qū);
lock := false; /*開鎖*/
end;

原理和上面的類似第一次交換:

// lock = false
// p0 = true
swap(p0,lock)
// p0 = false , lock = true

第二次交換

// lock = true
// p1 = true
swap(p0,lock)
// p1 = true , lock = true, 即swap(true,true)

線程被阻塞

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.解決信號(hào)量丟失和假喚醒 public class MyWaitNotify3{ MonitorObject m...
    Q羅閱讀 995評(píng)論 0 1
  • 一、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個(gè)執(zhí)行中的程序?qū)嵗總€(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程中可以有多個(gè)線程。...
    阿敏其人閱讀 2,688評(píng)論 0 13
  • 1. 什么情況下會(huì)有線程隱患? 我們?cè)谑褂枚嗑€程技術(shù)帶來的便利的同時(shí),也需要考慮下多線程所帶來的隱患。比如,我們可...
    沉江小魚閱讀 888評(píng)論 0 11
  • 在之前的課程中,我們已經(jīng)學(xué)習(xí)了進(jìn)程相關(guān)的知識(shí)。進(jìn)程是計(jì)算機(jī)程序被執(zhí)行的一個(gè)實(shí)例(instance),一個(gè)進(jìn)程可能由...
    夏威夷的芒果閱讀 1,054評(píng)論 0 2
  • 線程池ThreadPoolExecutor corepoolsize:核心池的大小,默認(rèn)情況下,在創(chuàng)建了線程池之后...
    irckwk1閱讀 863評(píng)論 0 0

友情鏈接更多精彩內(nèi)容