Part 6.系统编程之线程--2(同步,互斥锁,死锁)

本文详细介绍了在多线程编程中如何使用同步和互斥锁解决线程不安全问题,包括死锁的产生及避免方法,以及如何实现线程间的有序执行。

(一)同步

通过上一篇的介绍,我们发现了多线程开发可能遇到的问题,问题产⽣的原因就是没有控制多个线程对同⼀资源的访问,对数据造成破坏,使得线程运⾏的结果不可预期,这种现象称为“线程不安全”。

如何解决这个问题,就要引入同步,那什么是同步呢?

同步就是协同步调,按预定的先后次序进⾏运⾏。如:你说完,我再说。

"同"字从字⾯上容易理解为⼀起动作,其实不是,"同"字应是指协同、协助、互相配合。

如进程、线程同步,可理解为进程或线程A和B⼀块配合,A执⾏到⼀定程度时要依靠B的某个结果,于是停下来,示意B运⾏; B依⾔执⾏,再将结果给 A; A再继续操作。

所以对于之前提出的那个计算错误的问题,可以通过 线程同步 来进⾏解决,思路如下:

错误问题代码

1. 系统调⽤work1,然后获取到num的值为0,此时上⼀把锁,即不允许其他现在操作num

2. 对num的值进⾏+1并更新num

3. 解锁,此时num的值为1,其他的线程就可以使⽤num了,⽽且是num的值不是0⽽是1

4. 同理其他线程在对num进⾏修改时,都要先上锁,处理完后再解锁,在 上锁的整个过程中不允许其他线程访问,就保证了数据的正确性。

 

(二)互斥锁

什么是互斥锁?

我们知道当多个线程⼏乎同时修改某⼀个共享数据的时候,需要进⾏同步控制;

线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁;

互斥锁为资源引⼊⼀个状态:锁定/⾮锁定

某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“⾮锁定”,其他的线程才能再次锁定该资源。

互斥锁保证了每次只有⼀个线程进⾏写⼊操作, 从⽽保证了多线程情况下数据的正确性。

threading模块中定义了Lock类,可以⽅便的处理锁定:

1 from threading import Lock
2 
3 mutex = Lock()#创建一个锁对象
4 
5 mutex.acquire()#使这把锁上锁
6 
7 mutex.release()#解锁

其中,锁定⽅法acquire可以有⼀个blocking参数和timeout参数

  • 如果设定blocking为True,则当前线程会堵塞,直到获取到这个锁为⽌(如果没有指定,那么默认为True)
  • 如果设定blocking为False,则当前线程不会堵塞
  • 如果没有设定timeout即默认永远阻塞
  • 如果设定了timeout参数即会超时后跳过该阻塞区

所以用互斥锁修改代码后如下:

 1 from threading import Thread, Lock
 2 import time
 3 
 4 num = 0
 5 
 6 
 7 def work1():
 8     global num
 9     #上锁,只要其先上锁,后面的线程就得等着
10     mutex.acquire()
11     for i in range(1000000):
12         num += 1
13     #解锁,供后面的线程使用
14     mutex.release()
15     print("在work1中unm为:%d" % num)
16 
17 
18 def work2():
19     global num
20     #前面线程解锁后,他才可以开始上锁
21     mutex.acquire()
22     for i in range(1000000):
23         num += 1
24     #解锁
25     mutex.release()
26     print("在work2中unm为:%d" % num)
27 
28 
29 if __name__ == "__main__":
30     #创建一把锁
31     mutex = Lock()
32     t1 = Thread(target=work1)
33     t1.start()
34     t2 = Thread(target=work2)
35     t2.start()
36 
37 
38 》》》输出:
39 在work1中unm为:1000000
40 在work2中unm为:2000000

但是我们想一想,锁放在这个地方真的是最合适的吗?

我们知道此时加上锁,相当于多任务变单任务,即两个线程需要相互等待去抢锁,此时,我们需要知道,锁住的东西应该越少越好,因为这样,才能保证两个线程可以雨露均沾,否则,如上述所示加锁方式,work2线程就得等到work1完全执行完才能开始执行。

所以加锁时的一个原则是,锁住 应该上锁的最小部分。

所以最好应该在for循环里面上锁,锁住 num = num + 1 即可,这样两个线程可以各自竞争上锁,但又保证了num = num + 1 执行过程不会被打断。

小结:

在多线程开发中,全局变量是多个线程都共享的数据,⽽局部变量等是各⾃线程的,是⾮共享的,就算是两个线程运行同一块代码,但是内存会为其各自开辟一块内存,不会相互影响。

 

(三)死锁

在线程间共享多个资源的时候,如果两个线程分别占有⼀部分资源并且同时等待对⽅的资源,就会造成死锁。

尽管死锁很少发⽣,但⼀旦发⽣就会造成应⽤的停⽌响应。下⾯看⼀个死锁的例⼦:

 1 import threading
 2 import time
 3 
 4 
 5 class MyThread1(threading.Thread):
 6     def run(self):
 7         if mutexA.acquire():
 8             print(self.name + '--doing-up--')
 9             time.sleep(1)
10 
11             if mutexB.acquire():
12                 print(self.name + '--doing-down--')
13                 mutexB.release()
14             mutexA.release()
15 
16 
17 class MyThread2(threading.Thread):
18     def run(self):
19         if mutexB.acquire():
20             print(self.name + '--doing-up--')
21             time.sleep(1)
22 
23             if mutexA.acquire():
24                 print(self.name + '--doing-down--')
25                 mutexB.release()
26             mutexB.release()
27 
28 
29 if __name__ == "__main__":
30     mutexA = threading.Lock()
31     mutexB = threading.Lock()
32     t1 = MyThread1()
33     t2 = MyThread2()
34     t1.start()
35     t2.start()
36 
37 》》》输出:
38 Thread-1--doing-up--
39 Thread-2--doing-up--
40 一直不结束

此时就出现了死锁情况,分析如下:

因此避免死锁的方法就是:添加超时时间

实际上,在mutex.acquire()中可以添加超时时间,即超过设定时间后,我就不等待了,就直接跳过它继续运行。所以上述代码中如果在第一个里面添加timeout,那在超时时间以后,他就会跳过if语句块,解开A的锁,从而使第二个线程可以执行if语句块。

 

(四)同步应用----多个线程有序执行

 1 import threading
 2 import time
 3 
 4 class MyThread1(threading.Thread):
 5     def run(self):
 6         while True:
 7             #由于A没有事先上锁,所以此时满足条件
 8             if mutexA.acquire():
 9                 print('--Task1--')
10                 time.sleep(0.5)
11                 #解开B的锁,让B执行
12                 mutexB.release()
13                 #结束后,又得等待自己的锁被解开
14                 
15 
16 class MyThread2(threading.Thread):
17     def run(self):
18         while True:
19             #由于上面已经把B的锁解开了,此时满足条件运行
20             if mutexB.acquire():
21                 print('--Task2--')
22                 time.sleep(0.5)
23                 #解开C的锁,让C执行
24                 mutexC.release()
25                 # 结束后,又得等待自己的锁被解开
26 
27 
28 class MyThread3(threading.Thread):
29     def run(self):
30         while True:
31             # 由于上面已经把C的锁解开了,此时满足条件运行
32             if mutexC.acquire():
33                 print('--Task3--')
34                 time.sleep(0.5)
35                 # 解开A的锁,让A执行,从而达到一种按次序运行的效果
36                 mutexA.release()
37                 # 结束后,又得等待自己的锁被解开
38 
39 
40 if __name__ == "__main__":
41     mutexA = threading.Lock()
42     #先给B,C上锁
43     mutexB = threading.Lock()
44     mutexB.acquire()
45     mutexC = threading.Lock()
46     mutexC.acquire()
47 
48     t1 = MyThread1()
49     t2 = MyThread2()
50     t3 = MyThread3()
51     t1.start()
52     t2.start()
53     t3.start()
54 
55 
56 》》》输出:
57 --Task1--
58 --Task2--
59 --Task3--
60 --Task1--
61 --Task2--
62 --Task3--

可以使⽤互斥锁完成多个任务,有序的进行⼯作,这就是线程的同步!

 

转载于:https://www.cnblogs.com/boru-computer/p/9761596.html

标题基于Python的自主学习系统后端设计与实现AI更换标题第1章引言介绍自主学习系统的研究背景、意义、现状以及本文的研究方法创新点。1.1研究背景与意义阐述自主学习系统在教育技术领域的重要性应用价值。1.2国内外研究现状分析国内外在自主学习系统后端技术方面的研究进展。1.3研究方法与创新点概述本文采用Python技术栈的设计方法系统创新点。第2章相关理论与技术总结自主学习系统后端开发的相关理论技术基础。2.1自主学习系统理论阐述自主学习系统的定义、特征理论基础。2.2Python后端技术栈介绍DjangoFlask等Python后端框架及其适用场景。2.3数据库技术讨论关系型非关系型数据库在系统中的应用方案。第3章系统设计与实现详细介绍自主学习系统后端的设计方案实现过程。3.1系统架构设计提出基于微服务的系统架构设计方案。3.2核心模块设计详细说明用户管理、学习资源管理、进度跟踪等核心模块设计。3.3关键技术实现阐述个性化推荐算法、学习行为分析等关键技术的实现。第4章系统测试与评估对系统进行功能测试性能评估。4.1测试环境与方法介绍测试环境配置采用的测试方法。4.2功能测试结果展示各功能模块的测试结果问题修复情况。4.3性能评估分析分析系统在高并发等场景下的性能表现。第5章结论与展望总结研究成果并提出未来改进方向。5.1研究结论概括系统设计的主要成果技术创新。5.2未来展望指出系统局限性并提出后续优化方向。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值