多线程共享资源之竞态条件

发布时间 2023-07-29 12:09:26作者: Allen_Hao

什么是竞态条件?

竞态条件是指多个线程在访问和操作共享资源时,由于执行顺序的不确定性而导致结果不确定或出现错误。

示例1:

'''
竞态条件是指多个线程在访问和操作共享资源时,由于执行顺序的不确定性而导致结果不确定或出现错误。
'''

import threading
# 共享变量
counter = 0


# 线程函数
def increment():
    global counter
    '''
    在Python中,使用下划线 _ 作为一个临时变量的命名约定是很常见的。这个约定表明该变量的值不会被使用,或者不关心该变量的具体值。
    for _ in range(100000) 表示一个循环,循环执行10万次。在每次循环迭代中,由于并不需要使用循环变量的值,因此使用下划线 _ 来表示这个临时变量。
    这种情况下,使用下划线 _ 可以告诉读者,这个循环变量的值并不重要,只是为了控制循环次数而已。
    通过使用下划线 _,可以避免创建一个不必要的变量,并提高代码的可读性。
    '''
    for _ in range(100000):
        counter += 1


def decrement():
    global counter
    for _ in range(100000):
        counter -= 1


def main():
    # 创建两个线程
    t1 = threading.Thread(target=increment)
    t2 = threading.Thread(target=decrement)

    # 启动线程
    t1.start()
    t2.start()

    # 等待线程结束
    t1.join()
    t2.join()

    # 打印最终结果
    print("Counter:", counter)


if __name__ == '__main__':
    main()

输出:Counter: 0

多次执行从结果上,是对的。多线程存在竞态条件,可能导致结果不正确。(有些结果是正确的,有写结果是错误的,存在概率问题)

在这段代码中,两个线程分别对共享变量 counter 进行加一和减一操作。由于这两个线程同时访问和修改 counter 变量,可能会导致竞态条件的问题。

在某些情况下,当一个线程正在执行增加操作时,另一个线程可能同时执行减少操作,这可能导致结果不可预测。

然而,即使竞态条件发生,也不能保证最终结果一定是0。因为在 Python 中, counter += 1counter -= 1 操作并不是原子操作,它们实际上是多个步骤的组合。

例如,在 counter += 1 操作中,首先需要读取 counter 的当前值,然后进行加一操作,最后将新值写回到 counter 中。类似地,counter -= 1 操作也包括读取、减一和写回这三个步骤。

由于这些操作不是原子的,两个线程之间的交替执行可能会导致一些操作被覆盖或丢失,从而导致最终结果出现错误。

示例2 

 1 import threading
 2 
 3 
 4 # 全局变量
 5 g_num = 0
 6 
 7 
 8 # 对g_num进行加操作
 9 def sum_num1():
10     for i in range(1000000):
11         global g_num
12         g_num += 1
13 
14     print("g_num1:", g_num)
15 
16 
17 # 对g_num进行加操作
18 def sum_num2():
19     for i in range(1000000):
20         global g_num
21         g_num += 1
22 
23     print("g_num2:", g_num)
24 
25 
26 if __name__ == '__main__':
27     # 创建子线程
28     sum1_thread = threading.Thread(target=sum_num1)
29     sum2_thread = threading.Thread(target=sum_num2)
30 
31     # 启动线程
32     sum1_thread.start()
33     sum2_thread.start()
34     sum1_thread.join()
35     sum2_thread.join()
36     print("Final g_num:", g_num)

输出:(存在出现问题的概率,模拟出来有概率)

g_num1: 1295051
g_num2: 1999991
Final g_num: 1999991

解决方法

为了解决这个问题,可以使用同步机制(如锁)来确保每次只有一个线程能够访问和修改共享变量

 1 import threading
 2 
 3 # 全局变量
 4 g_num = 0
 5 # 全局变量作为锁对象(保证多个线程使用的是同一把锁)。在多个线程中通过 with lock: 语句来获取该锁。
 6 lock = threading.Lock()
 7 
 8 
 9 # 对g_num进行加操作
10 def sum_num1():
11     global g_num
12     for i in range(1000000):
13         with lock:
14             g_num += 1
15     print("g_num1:", g_num)
16 
17 
18 # 对g_num进行加操作
19 def sum_num2():
20     global g_num
21     for i in range(1000000):
22         with lock:  # 通过with语句实现自动上锁、解锁
23             g_num += 1
24     print("g_num2:", g_num)
25 
26 
27 if __name__ == '__main__':
28     # 创建子线程
29     sum1_thread = threading.Thread(target=sum_num1)
30     sum2_thread = threading.Thread(target=sum_num2)
31 
32     # 启动线程
33     sum1_thread.start()
34     sum2_thread.start()
35 
36     # 等待子线程结束
37     sum1_thread.join()
38     sum2_thread.join()
39 
40     # 打印最终结果
41     print("Final g_num:", g_num)

输出:

g_num1: 1841952
g_num2: 2000000
Final g_num: 2000000