day6.系统编程之线程--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--

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

猜你喜欢

转载自www.cnblogs.com/boru-computer/p/9761596.html
今日推荐