如果多个线程同时对同一个全局变量操作,会出现资源竞争问题,从而数据结果会不正确,
程序示例:
import threading
import time
num1 = 0
def demo1(a):
global num1
for i in range(a):
num1 += 1
print("在demo1中,num1的值为:",num1)
def demo2(a):
global num1
for i in range(a):
num1 += 1
print("在demo2中,num1的值为:",num1)
def main():
nums = 1000000
t1 = threading.Thread(target = demo1, args=(nums,))
t2 = threading.Thread(target = demo2, args=(nums,))
t1.start()
t2.start()
# 主线程等待两个子线程执行完毕
time.sleep(5)
print("在main中,num1的值为:",num1)
if __name__ == '__main__':
main()
程序执行结果:
理论上 num1经过两个线程自加nums = 1000000次,最后应该得到num1 =2000000,可以看出这并不是我们预料到的结果,出现了严重的偏差,两个线程互相对num1进行加一赋值操作造成结果混乱,这就是多线程的资源竞争问题。
那么,如何解决资源竞争问题?
当存在多个线程或者进程同时调用一个全局变量资源,并且会对它进行修改时,可以采用上锁这个方法,来解决资源竞争问题。采用全局变量锁,每当线程调用全局变量时,就将该资源上锁,不允许被调用,只有当调用结束后才打开锁,这里引入互斥锁,能够保证全局变量资源的安全。
互斥锁的定义:当在请求之前,资源没有被上锁,那么请求并不会堵塞;如果在请求之前,是被锁上的, 那么请求就处于一个堵塞状态,只有当前得锁被打开之后,才能再次上锁。
互斥锁格式:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
针对以上问题,咱们试着用互斥锁解决,只需要将num1 += 1 锁住保持原子性就可以了,最后提一点:锁住的代码越少越好,,改善程序如下:
import threading
import time
num1 = 0
def demo1(a):
global num1
for i in range(a):
lock_1.acquire() # 上锁
num1 += 1 # 锁住 保持原子性
lock_1.release() # 开锁
print("在demo1中,num1的值为:",num1)
def demo2(a):
global num1
for i in range(a):
lock_1.acquire() # 上锁
num1 += 1 # 锁住 保持原子性
lock_1.release() # 开锁
print("在demo2中,num1的值为:",num1)
# lock_1.release() # 开锁
# 创建一个互斥锁,,默认是不上锁的状态
lock_1 = threading.Lock()
def main():
nums = 1000000
t1 = threading.Thread(target = demo1, args=(nums,))
t2 = threading.Thread(target = demo2, args=(nums,))
t1.start()
t2.start()
# 主线程等待两个子线程执行完毕
time.sleep(2)
print("在main中,num1的值为:",num1)
if __name__ == '__main__':
main()
执行结果:
可以看出,虽然有一个线程结果并不理想,但已经很接近了,用互斥锁改善了很多,实际上我们最后需要的就是main里的结果,只需要保证最后我们需要的结果是正确的就行了。
互斥锁解决资源竞争的好处:互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
互斥锁的优缺点:
优点:确保了某段关键代码只能由一个线程从头到尾完整地执行。
缺点:阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了,由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁.