多线程锁原理

多线程锁原理

  • 临界区: 在临界区内,会对共享资源进行访问和修改
  • 共享资源: 在同一时间只能被单个线程所持有

访问临界区过程:

  1. 申请临界区权限
  2. 访问临界区
  3. 归还权限, 退出临界区

线程安全问题:
12306卖票问题,既不能多卖又不能少卖

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

如果多个线程,同时操作的话,可能会造成多卖票,本来只有1000张票,却卖个1001个人

为了避免这种情况,可以尝试使用标志位来解决

var isSaling = false // 正在售票标志位

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

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

遗憾的是这种方式,仍然无法解决安全问题,关键在于查询操作和修改操作并非不可中断,造成多个程序进入临界区:

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

为了防止这种情况发生。于是决定设置两个标志位


var isSaling1 = false // 正在售票标志位
var isSaling2 = false // 正在售票标志位
// =====================
// thread 1
while isSaling2 { // 2号线程正在售票,稍微等等
}

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

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

while isSaling1 { // 1号线程正在售票,稍微等等
}

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


这种思维方式就像,有一条公家的小板凳,我先看下隔壁有没有占用那条小板凳,没有的话我就拿过来坐坐

但是仍然会发生多个线程进入临界区的问题: ** isSaling1 = false, isSaling2 = false, thread1 exec to while isSaling2 expr and thread2 exec to while isSaling1 expr **

可见光增加flag无法解决问题,换一种方式增加临界区大小


var isSaling1 = false // 正在售票标志位
var isSaling2 = false // 正在售票标志位
// =====================
// thread 1
isSaling1 = true // 表示我正在售票,你们先等等
while isSaling2 { // 2号线程正在售票,稍微等等
}


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

// thread 2
// =================================
isSaling2 = true // 表示我正在售票,你们先等等
while isSaling1 { // 1号线程正在售票,稍微等等
}


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


这种方式却会造成死锁,死锁的时机和之前一样。** isSaling1 = false, isSaling2 = false, thread1 exec to while isSaling2 expr and thread2 exec to while isSaling1 expr **, 只不过一个是造成多个线程共同访问临界区,一个是造成死锁

为了解决这个问题,产生了软件上的解法和硬件上的解法,
硬件的解法即源语。

软件上:
这两种算法思想其实是一致的,就是当线程进入临界区之后,发现别的线程也进入了临界区,怎么办?如何避免死锁或者并非, 这个时候就需要制定一个规则让他们能够正确运行
** Dekker 算法


var isSaling1 = false // 正在售票标志位
var isSaling2 = false // 正在售票标志位
var turn = 1
// =====================
// thread 1
isSaling1 = true // 表示我正在售票,你们先等等
while isSaling2 { // 这个时候发现2号正在售票
   if turn == 2 {
        isSaling1 = fasle // 我不卖了,让2号先卖
        while(isSaling2) {} // 等待2号卖票
        isSaling1 = true // 2号已经卖完票了轮到我卖了 
   }
}


if more_ticket > 0 {
 // 卖票 
 more_ticket -= 1
} 
isSaling1 = false // 卖完票了,你们可以操作了 
turn = 2
// thread 2
// =================================
isSaling2 = true // 表示我正在售票,你们先等等
while isSaling1 { // 1号线程正在售票,稍微等等
   if turn == 1 {
        isSaling2 = fasle 
        while(isSaling1) {} 
        isSaling2 = true 
   }
}


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

这里turn = 1时,发生碰撞1号优先卖票,等于2时则相反,双方不断的切换优先级,防止死锁的同时避免多线程并发进入临界区

** 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 不在其临界区内*/ /* P2 不在其临界区内*/
           inside[1]:= true;
           turn := 2;
           while (inside[2] and turn=2)
do begin end;
             临界区;
           inside[1] := false;
       end;
       process P2
       begin
           inside[2] := true;
           turn := 1;
           while (inside[1] and turn=1)
do begin end; 
             临界区;
           inside[2] := false;
       end;
coend.

这个算法的规则更加简化,由于两个线程都会修改turn的值,那么最后只有一个线程执行,另外一个堵塞,画个表看下会更加清晰

Thread 1 Thread 2
inside[1]:= true inside[2] := true
turn := 2
if inside[2]
if turn := 2
// 线程1 被堵塞
turn := 1
// 线程1 执行 while (inside[1] and turn=1) 条件为真,线程被堵塞
inside[1] := false
线程2 执行

这个算法还是很精髓的

硬件指令

硬件指令相对于软件算法的不同在于不可分割性,也就是不会被中断,操作一起呵成

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; /*上锁*/ 临界区;
s := true; /*开锁*/
end;

关键句子: pi := TS(s)

// s = true
// =============
p0 = TS(s) 
// 执行之后
// s = false , p0 = true ,线程不会被阻塞

// ==========
// 由于TS指令的特殊性,导致执行的时候必然会有先后顺序,哪怕两个线程是同时调用的该指令,这里假定p0线程在前,p1在后
// 此时  s = false
p1 = TS(s) 
//  执行之后
//  p1 = false , s = false , set 失败, 线程被阻塞,直到p0 设置 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;/*上锁*/ 临界区;
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)

线程被阻塞

你可能感兴趣的:(多线程锁原理)