多线程锁原理

多线程锁原理

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

访问临界区过程:

  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)

线程被阻塞

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,386评论 6 479
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,939评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,851评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,953评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,971评论 5 369
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,784评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,126评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,765评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,148评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,744评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,858评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,479评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,080评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,053评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,278评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,245评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,590评论 2 343

推荐阅读更多精彩内容

  • 1.解决信号量丢失和假唤醒 public class MyWaitNotify3{ MonitorObject m...
    Q罗阅读 871评论 0 1
  • 一、进程和线程 进程 进程就是一个执行中的程序实例,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。...
    阿敏其人阅读 2,607评论 0 13
  • 1. 什么情况下会有线程隐患? 我们在使用多线程技术带来的便利的同时,也需要考虑下多线程所带来的隐患。比如,我们可...
    沉江小鱼阅读 806评论 0 11
  • 在之前的课程中,我们已经学习了进程相关的知识。进程是计算机程序被执行的一个实例(instance),一个进程可能由...
    夏威夷的芒果阅读 899评论 0 2
  • 线程池ThreadPoolExecutor corepoolsize:核心池的大小,默认情况下,在创建了线程池之后...
    irckwk1阅读 714评论 0 0