Python基础进阶8:多线程共享资源,常用技巧,threading库

你好,我是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()会让代码显得更加简洁。

本文原始版本发表链接:

https://mp.weixin.qq.com/s?__biz=MzI2Mjg3NTY5MQ==&mid=2247484822&idx=1&sn=02d51199c6120fb59ac9b3d36063008c&chksm=ea453ad2dd32b3c405183a3f9d60dab39e51b495c8bf6f11b3376c0ba9667be1948aa480a32f#rd

kelly会在公众号「kelly学技术」不定期更新文章,感兴趣的朋友可以关注一下,期待与您交流。

--over--

  • 23
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值