1. 多线程共享全局变量验证
多线程通常用来完成多任务,但多任务之间通常需要共享数据(比如:一条子线程修改,一条子线程读取)。那么自然会有这样的问题:主线程内的多条子线程是否共享全局变量。
首先运行下述代码,观察输出:
import threading
# 定义一个全局变量
global_num = 100
def test1():
global global_num
global_num += 1
print("Inside the Thread Denoted by "
"Function test1() num = %d" % global_num)
def test2():
print("Inside the Thread Denoted by "
"Function test2() num = %d" % global_num)
def main():
thread1 = threading.Thread(target=test1)
thread2 = threading.Thread(target=test2)
thread1.start()
thread2.start()
print("Inside the Main Thread num = %d" % global_num)
if __name__ == "__main__":
main()
上述代码的运行结果为:
Inside the Thread Denoted by Function test1() num = 101
Inside the Thread Denoted by Function test2() num = 101
Inside the Main Thread num = 101
所以,Python中,主线程内的多条子线程共享全局变量。
2. 多线程共享全局变量的资源竞争
2.1 资源竞争问题描述
首先,下面的代码希望实现这样一个功能:定义一个全局变量,通过两个线程分别对该变量进行累加1000000次,希望最后实现该全局变量值为2000000,请运行下述代码,观察其结果是否和你想象的一致。
import threading
import time
# 定义一个全局变量
global_num = 0
def test1(num):
global global_num
for i in range(num):
global_num += 1
print("Inside the Thread Denoted by "
"Function test1() num = %d" % global_num)
def test2(num):
global global_num
for i in range(num):
global_num += 1
print("Inside the Thread Denoted by "
"Function test2() num = %d" % global_num)
def main():
thread1 = threading.Thread(target=test1, args=(1000000,))
thread2 = threading.Thread(target=test2, args=(1000000,))
thread1.start()
thread2.start()
# 等待两个线程均运行结束
time.sleep(5)
print("Inside the Main Thread num = %d" % global_num)
if __name__ == "__main__":
main()
上述代码的运行结果为:
Inside the Thread Denoted by Function test1() num = 1356018
Inside the Thread Denoted by Function test2() num = 1470161
Inside the Main Thread num = 1470161
上述结果表明,最终运行输出并非我们所想要的2000000,但是原因何在?
2.2 资源竞争产生原因
因为最后变量global_num
并未按照期望增加至2000000
,所以问题必然出现在语句global_num += 1
处。
实际上,之所以产生意料之外的结果,原因在于:如上图所示,虽然计算机看起来通过两条线程同时分别执行一个任务,但是:
- 语句
global_num += 1
在被CPU执行前会被解释器编译为多条语句,如图中的3条语句,这才是CPU执行的原子级语句; - CPU通过时间片轮转的方式分别执行两条线程的原子语句。
基于上述两条原因,让我们分析一种情况,尝试解释为什么会出现上述意料之外的情形:
- 线程1的两条原子语句(1.CPU获取
global_num
的值;2.CPU将global_num
的值加1)得到执行后,CPU暂停执行该线程,并执行线程2; - 线程2的两条原子语句(1.CPU获取
global_num
的值;2.CPU将global_num
的值加1)得到执行后,CPU暂停执行该线程,重新执行线程1的一条原子语句(3.将第2步的结果存储到global_num
中); - 线程2的一条原子语句(3.将第2步的结果存储到
global_num
中)得到CPU执行。
通过对上述情形的分析,可以发现虽然CPU执行了两次global_num += 1
语句,但是最终变量global_num
的值只被增加了1。