多线程之资源竞争与互斥锁
资源竞争
import threading
import time
num = 0
def add_num_one(count):
global num
for _ in range(count):
num +=1
print(f'add_num_one结果: {num}')
def add_num_two(count):
global num
for _ in range(count):
num +=1
print(f'add_num_two结果: {num}')
def main():
print(f'原始数字: {num}')
thread_one = threading.Thread(target=add_num_one, args=(1000000,))
thread_one.start()
thread_two = threading.Thread(target=add_num_two, args=(1000000,))
thread_two.start()
while threading.active_count() != 1:
time.sleep(0.5)
print(f'最终数字: {num}')
if __name__ == '__main__':
main()
输出:
原始数字: 0
add_num_one结果: 1126618
add_num_two结果: 1382806
最终数字: 1382806
解释:
add_num_one和add_num_two的参数都是1000000,但最终数字竟然不是2000000,是因为发生了资源竞争,即多个线程同时对一个全局变量操作,会出现资源竞争,导致数据结果不准确
但需要注意的是,如果参数不是1000000而是1000,结果可能是正确的,此参数存在一个临界点,跟cpu频率有关,但结果正确不能表示不会发生资源竞争,只是因为cpu速度太快,在进程调度之前已经将结果计算完毕,导致数据正确
互斥锁
import threading
import time
num = 0
# 互斥锁
mutex_lock = threading.Lock()
def add_num_one(count):
global num
mutex_lock.acquire()
for i in range(count):
num +=1
mutex_lock.release()
print(f'add_num_one结果: {num}')
def add_num_two(count):
global num
mutex_lock.acquire()
for i in range(count):
num +=1
mutex_lock.release()
print(f'add_num_two结果: {num}')
def main():
print(f'原始数字: {num}')
thread_one = threading.Thread(target=add_num_one, args=(1000000,))
thread_one.start()
thread_two = threading.Thread(target=add_num_two, args=(1000000,))
thread_two.start()
while threading.active_count() != 1:
time.sleep(0.5)
print(f'最终数字: {num}')
if __name__ == '__main__':
main()
输出:
原始数字: 0
add_num_one结果: 1000000
add_num_two结果: 2000000
最终数字: 2000000
结论:互斥锁解决了资源竞争的问题
修改核心代码如下:
def add_num_one(count):
global num
for i in range(count):
# 精确加锁,优点是精确针对发生资源竞争的核心代码,缺点是频繁的加锁解锁
mutex_lock.acquire()
num +=1
mutex_lock.release()
print(f'add_num_one结果: {num}')
def add_num_two(count):
global num
# 粗略加锁,优点时减少加锁解锁的频率,缺点是无法精确定位发生资源竞争的核心代码
mutex_lock.acquire()
for i in range(count):
num +=1
mutex_lock.release()
print(f'add_num_two结果: {num}')
输出:
原始数字: 0
add_num_two结果: 1042738
add_num_one结果: 2000000
最终数字: 2000000
结果不再是 add_num_one的1000000和add_num_two的2000000
解释:
cpu在调度进程,当调度到add_num_two,因为互斥锁加在循环外,所以大部分资源是在进行 num+=1 的操作
而add_num_one的互斥锁加在循环内,大部分资源是在进行加锁和解锁,所以比add_num_two慢
最终导致add_num_two的最终结果在1000000到2000000之间,add_num_one最终结果一定是2000000