跳转至

多线程之资源竞争与互斥锁


资源竞争

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