你好,我是kelly,今天分享:Python多线程共享资源、threading库的使用。
背景知识:线程由进程创建,共享同一个进程的全局资源。
一、使用场景1:共享资源
描述:在使用多线程时,需要汇总不同线程的运行结果。比如现在有2个线程thread1、thread2,需要将这2个线程运行结果汇总在同一个全局变量中。
解决方法:在线程外面定义全局变量,让全局变量在多个线程中共享。
示例:
import threading
# 创建全局变量,全局变量在不同线程之间共享
global_value = {}
def process_thread(thread_id, value):
global global_value
global_value[thread_id] = value
print()
t1 = threading.Thread(target=process_thread, args=(0, '线程1的数据',))
t2 = threading.Thread(target=process_thread, args=(1, '线程2的数据',))
t1.start()
t2.start()
t1.join()
t2.join()
print(global_value)
运行结果:
{0: '线程1的数据', 1: '线程2的数据'}
在全局变量global_value中,为不同线程创建不同的key,将不同线程执行结果作为value。
二、使用场景2:竞争资源
描述:运行多线程时,如果不同线程对同一个全局变量进行修改,则会导致错误结果。
示例:
import threading
COUNT = 0
def process_thread(thread_id):
# 在线程中使用对全局变量修改,需要使用global关键字声明
global COUNT
for _ in range(500000):
COUNT += 1
print(f"线程{thread_id}", id(COUNT), COUNT)
if __name__ == "__main__":
t1 = threading.Thread(target=process_thread, args=(0,))
t2 = threading.Thread(target=process_thread, args=(1,))
t1.start()
t2.start()
t1.join()
t2.join()
运行结果:
线程0 2773793397680 847823
线程1 2773793397552 1000000
两个线程对全局变量COUNT分别累加500000,按理说线程0的累加结果为500000,实际值却是847823。
产生这一结果的原因为:线程1和线程2同时运行,对全局变量COUNT执行+=1操作时会存在交叉。比如线程1内部COUNT为10时,CPU转到执行线程2将COUNT修改为11,CPU再次转回到线程1执行+=1,这时COUNT就不是10,而是11,导致错误发生。
解决方法:使用线程锁机制,同个时刻只有一个线程能执行指定代码片段,从而防止不同线程在同一时间对全局资源进行修改。
使用threading.Lock()创建全局锁lock,线程内部对全局变量修改时,需要先获取锁后才能执行,执行完成后释放锁。获取锁和释放锁的操作,使用with关键字实现。
import threading
COUNT = 0
lock = threading.Lock() # 创建锁
def process_thread(thread_id):
# 在线程中使用对全局变量修改,需要使用global关键字声明
global COUNT
with lock:
for _ in range(500000):
COUNT += 1
print(f"线程{thread_id}", id(COUNT), COUNT)
if __name__ == "__main__":
t1 = threading.Thread(target=process_thread, args=(0,))
t2 = threading.Thread(target=process_thread, args=(1,))
t1.start()
t2.start()
t1.join()
t2.join()
运行结果:
线程0 1845249465168 500000
线程1 1845249465264 1000000
说明:使用with关键字时,可以自动实现加锁和解锁操作。
with lock:
for _ in range(500000):
COUNT += 1
print(f"线程{thread_id}", id(COUNT), COUNT)
等价于
lock.acquire() # 首先获取到锁,即加锁
try:
for _ in range(500000):
COUNT += 1
print(f"线程{thread_id}", id(COUNT), COUNT)
finally:
lock.release() # 执行完成后,释放锁/解锁
三、threading.local()使用
使用场景:①在不同线程内部使用全局变量、②且会对全局变量进行修改,③但又不想在线程内部传递局部变量。
1、不使用threading.local():
在process函数中访问局部变量thread_id,和value,需要传参。
import threading
def process(thread_id, value):
# 不同线程共享全局变量local,但不同线程有各自的局部变量thread_id和value
print("线程:", thread_id, value)
def process_thread(thread_id, value):
# 局部变量传参
process(thread_id, value)
t1 = threading.Thread(target=process_thread, args=(0, '线程1的数据',))
t2 = threading.Thread(target=process_thread, args=(1, '线程2的数据',))
t1.start()
t2.start()
t1.join()
t2.join()
print()
运行结果:
线程: 0 线程1的数据
线程: 1 线程2的数据
2、使用threading.local():
在process函数中访问变量thread_id,和value,不需要传参。
import threading
# 创建全局变量,全局变量在不同线程之间共享
local = threading.local()
def process():
# 不同线程共享全局变量local,但不同线程有各自的局部变量thread_id和value
print(id(local), "线程:", local.thread_id, local.value)
def process_thread(thread_id, value):
local.thread_id = thread_id
local.value = value
# 调用函数不需要传递全局变量local
process()
t1 = threading.Thread(target=process_thread, args=(0, '线程1的数据',))
t2 = threading.Thread(target=process_thread, args=(1, '线程2的数据',))
t1.start()
t2.start()
t1.join()
t2.join()
print()
运行结果:
1977591808224 线程: 0 线程1的数据
1977591808224 线程: 1 线程2的数据
因此,使用threading.local()会让代码显得更加简洁。
本文原始版本发表链接:
kelly会在公众号「kelly学技术」不定期更新文章,感兴趣的朋友可以关注一下,期待与您交流。
--over--