Python多线程共享全局变量的竞态问题与解决方案
本文探讨Python多线程共享全局变量时因执行顺序不确定导致的竞态条件问题。通过示例展示直接累加可能产生错误结果,并对比轮询方式与通知方式(如全局标志位)的效率差异。最终推荐使用Lock锁机制确保数据一致性,保证多线程环境下全局变量更新的原子性与正确性。
通常情况下,在多线程编程中,多个线程会共享同一块内存资源。
from threading import Thread
global_num = 0
def func1():
global global_num
for i in range(1000000):
global_num += 1
print('---------func1:global_num=%s--------'%global_num)
def func2():
global global_num
for i in range(1000000):
global_num += 1
print('--------fun2:global_num=%s'%global_num)
print('global_num=%s'%global_num)
lock = Lock()
t1 = Thread(target=func1)
t1.start()
t2 = Thread(target=func2)
t2.start()
运行上述代码后,观察到的输出结果如下:
global_num=0 ---------func1:global_num=1492752-------- --------fun2:global_num=1515462
#与多进程不同,每个进程拥有独立的资源块,多线程共享主线程的资源块(尽管这种表述在严格意义上并不完全准确)。
#这种共享机制虽然简化了线程间的数据传递,但由于线程执行的调度顺序具有不确定性,最终输出的结果往往并非预期的正确值。
#以下示例中,若未设置global_flag全局变量作为同步标志,逻辑上预期最终结果应为2000000(选择如此大的数值是为了更显著地暴露问题)。
#然而实际运行结果通常小于2000000。虽然理论上存在偶然得到2000000的理想情况,但这属于极小概率事件。造成这种现象的原因何在?
#根本原因在于CPU对线程调度的顺序是不确定的。具体而言,主线程创建了两个子线程t1和t2,分别指向func1()和func2()函数。
#在这两个线程的函数体中,均包含“global_num += 1”这一行代码。在计算机底层执行时,该语句实际分为两个步骤:第一步是从内存读取global_num的当前值并完成加一运算,此时global_num的值仍为旧值;第二步是将计算后的新值写回global_num,此时global_num的值才真正更新。在程序执行过程中,可能出现以下情况:
#当CPU执行线程t1中的累加语句时,在执行完第一步(读取并计算)后暂停,将线程t1挂起,转而执行线程t2。当线程t2执行一段时间后,也发生类似情况,
#随后CPU重新调度执行线程t1,此时正好执行累加语句的第二步(赋值)。由于t1和t2读取的是相同的旧值,t1的赋值覆盖了t2未完成的计算,导致这段时间内的累加操作失效,因此最终结果往往不等于2000000。
#解决此类问题可以采用引入额外变量并利用“轮询”方式执行,但这种方式效率低下且浪费CPU资源,因此通常采用“通知”机制来处理。
轮询方式实现如下:
from threading import Thread
global_num = 0
global_flag = 0
def func1():
global global_num
global global_flag
if global_flag == 0:
for i in range(1000000):
global_num += 1
global_flag = 1
print('---------func1:global_num=%s--------'%global_num)
def func2():
global global_num
while True:
if global_flag != 0:
for i in range(1000000):
global_num += 1
break
print('--------fun2:global_num=%s'%global_num)
print('global_num=%s'%global_num)
t1 = Thread(target=func1)
t1.start()
t2 = Thread(target=func2)
t2.start()
运行结果:
global_num=0 ---------func1:global_num=1000000-------- --------fun2:global_num=2000000
通知方式实现如下:
from threading import Thread,Lock
global_num = 0
def func1():
global global_num
for i in range(1000000):
lock.acquire()#两个线程会最开始抢这个锁,拿到锁就会处于关锁,执行后面的程序,其他线程执行处于监听状态,等待这个线程开锁,再抢锁
global_num += 1
lock.release()
print('---------func1:global_num=%s--------'%global_num)
def func2():
global global_num
for i in range(1000000):
lock.acquire()
global_num += 1
lock.release()
print('--------fun2:global_num=%s'%global_num)
print('global_num=%s'%global_num)
lock = Lock()
t1 = Thread(target=func1)
t1.start()
t2 = Thread(target=func2)
t2.start()
输出结果:
global_num=0 ---------func1:global_num=1901175-------- --------fun2:global_num=2000000
以上关于Python多线程处理同一全局变量的进阶方法即为本文全部内容,希望能为读者提供参考,也欢迎大家继续支持本站。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









