互斥锁
1.互斥锁的概念
2.互斥锁的使用
3.使用互斥锁完成2个线程对同一全局变量各加100万次的操作,而不会出现问题
死锁
死锁的概念
避如何免死锁:
出现死锁的例子
互斥锁
1.互斥锁的概念
- 互斥锁: 对共享数据进行锁定,保证同一时刻只有一个线程去操作
- 互斥锁是多个线程一起去抢,抢到锁的线程先执行,没有抢到锁的线程需要等待,等互斥锁使用完释放后,其他等待的线程再去抢这个锁。
2.互斥锁的使用
- threading模块中定义了Lock变量,这个变量本质上是一个函数,通过调用这个函数可以获取一把互斥锁。
互斥锁使用步骤
- 创建锁
mutex = threading.Lock()
- 上锁
mutex.acquire() ''' '' ' ...这里编写代码,能保证同一时刻只能有一个线程去操作,对共享数据进行锁定
- 释放锁
mutex.release()
注意点:
- acquire和release方法之间的代码同一时刻只能有一个线程去操作
- 如果在调用acquire方法的时候,其他线程已经使用了这个互斥锁,那么此时acquire方法会堵塞,直到这个互斥锁释放后才能再次上锁
3.使用互斥锁完成2个线程对同一全局变量各加100万次的操作,而不会出现问题
import threading
# 全局变量
g_num = 0
# 创建互斥锁
lock = threading.Lock()
# 循环100万次执行的任务
def task1():
# 表示要声明修改全局变量的内存地址
global g_num
# 上锁
lock.acquire()
# 每循环一次给全局变量加一
for i in range(1000000):
g_num += 1
# 释放锁
lock.release()
print("task1: ", g_num)
# 循环100万次执行的任务
def task2():
# 表示要声明修改全局变量的内存地址
global g_num
# 上锁
lock.acquire()
# 每循环一次给全局变量加一
for i in range(1000000):
g_num += 1
# 释放锁
lock.release()
print("task2: ", g_num)
if __name__ == '__main__':
first_thread = threading.Thread(target=task1)
second_thread = threading.Thread(target=task1)
first_thread.start()
second_thread.start()
运行结果:
- 互斥锁可以保证同一时刻只有一个线程去执行代码,能够保证全局变量的数据没有问题
- 线程等待和互斥锁都是把多任务改成单任务去执行,保证了数据的准确性,但是执行性能会下降
死锁
死锁的概念
- 死锁: 一直等待对方释放锁的情景就是死锁
- 死锁是一种状态,有多个互斥锁时,就有可能出现死锁这种状态。
- 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁
避如何免死锁:
- 程序设计时要尽量避免(银行家算法) 什么是银行家算法?
- 添加超时时间等
出现死锁的例子
# 需求: 多线程同时根据下标在列表中取值,要保证同一时刻只能有一个线程取值
import threading
# 创建锁
lock = threading.Lock()
def get_value(index):
# 上锁
lock.acquire()
my_list = [0, 1, 2]
# 判断下标是否越界
if index >= len(my_list):
print("下标越界:", index)
'''
如果加上下方代码就不会造成死锁,要把锁给释放掉
取值不成功,也需要释放互斥锁,不要影响后面的线程去取值
需要在合适的地方进行释放锁,防止死锁
lock.release()
'''
# 这块直接返回了,不执行后面的代码了
return
# 根据下标取值
value = my_list[index]
print(value)
# 释放锁
lock.release()
if __name__ == '__main__':
for i in range(10):
sub_thread = threading.Thread(target=get_value, args=(i,))
sub_thread.start()
运行结果: 程序一直在执行,一直等待释放锁,这就会造成了死锁