Python threading Local()函数用法案例详解
作者:码农飞哥 发布时间:2021-11-27 21:57:02
前言
当多线程访问同一个公共资源时,如果涉及到修改该公共资源的操作就可能会出现由于数据不同步导致的线程安全问题。一般情况下我们可以通过给公共资源加互斥锁的方式来处理该问题。
当然,除非必须将多线程使用的资源设置为公共资源的情况。如果一个资源不需要在多个线程之间共享。我们也可以使用Python threading模块提供的local()方式来避免线程安全问题。
Python threading模块的local()函数跟Java中的ThreadLocal类有诸多类似的地方,感兴趣的小伙伴可以看下Java版的ThreadLoalJava ThreadLocal原理解析以及应用场景分析案例详解
local() 函数是什么?
threading的local()函数主要是用来封装公共资源,使得同一个公共资源在不同线程之间得以隔离。这句话该如何理解呢?举个例子说明下!假设现在有一个大箱子(相当于公共资源),每个人(相当于各个线程)将自己的手机放入这个大箱子里。如果不做任何控制的话,当人们从大箱子中取出手机时极有可能会出现取错的情况(找不到自己当初放入的手机)。而使用local()函数的话,就相当于对这个大箱子进行管理。当每个人放入手机的时候做一个标记(比如在手机上标记所有者的姓名)并隔离放置到箱子中。这样当人们从大箱子中取出手机就能准确的找到自己当初放入的手机。
调用local()函数会生成一个ThreadLocal对象,该对象是所有线程都能访问的,就像上面例子中的大箱子。但是,放入到ThreadLocal对象中的变量则是各个线程所独有的,随便变量名相同,但是指向的值则是完全不同的。
local()函数如何用?
local()函数使用的基本语法是:
import threading
local=threading.local()
第一步就是引入threading模块,第二步就是调用local()函数得到全局的Threadlocal对象。这样说始终是有点干涩,没味道。那么就给代码加点盐吧。还是从那个大箱子说起。
1. 不做标记,不做隔离
第一个示例代码就是所有人将自己的手机放入大箱子里,不做标记,不做隔离。先放入,过一段时间后再取出。
import threading
import time
def set_telephone(telephone):
global global_telephone
global_telephone = telephone
print(threading.current_thread().name + " 放入的手机是", global_telephone)
time.sleep(1)
get_telephone()
def get_telephone():
print(threading.current_thread().name + " 取出的手机是", global_telephone)
if __name__ == '__main__':
for i in range(3):
thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
thread.start()
运行结果是:
学生0 放入的手机是 手机0
学生1 放入的手机是 手机1
学生2 放入的手机是 手机2
学生0 取出的手机是 手机2
学生1 取出的手机是 手机2
学生2 取出的手机是 手机2
这里有三个线程,分别模拟学生0,学生1,学生2 将各种的手机赋值给一个全局变量global_telephone(大箱子),然后取全局变量global_telephone中的值。可以看出取出的结果都变成了手机2。这显然没有达到我们的预期结果。这就是不加控制的后果。
2.使用local()函数加以控制
使用local()函数控制的话,就是将全局变量替换成ThreadLoal对象,由他来管理每个线程中的值。
import threading
import time
def set_telephone(telephone):
local.telephone = telephone
print(threading.current_thread().name + " 放入的手机是", local.telephone + "\n")
time.sleep(1)
get_telephone()
def get_telephone():
print(threading.current_thread().name + " 取出的手机是", local.telephone + "\n")
if __name__ == '__main__':
local = threading.local()
for i in range(3):
thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
thread.start()
运行结果是:
学生0 放入的手机是 手机0
学生1 放入的手机是 手机1
学生2 放入的手机是 手机2
学生1 取出的手机是 手机1
学生0 取出的手机是 手机0
学生2 取出的手机是 手机2
可以看出每个学生放入的手机和最终取出的手机是一致的。那么threading的local()函数是如何实现这一效果的呢?我们在这里不妨做一个推理。应该是将手机和它的主人做了一层映射关系。根据主人的唯一标识来寻找自己的手机。
3. 模拟实现local()的功能,创建一个箱子
前面我们推测我们需要定义一个全局的字典来存放每个学生各自放入的手机,字典的键是线程ID,值是指定的键值对。示例代码如下:
import threading
import time
global_goods_dict = {}
# {
# "线程ID":{"telephone":"放入的具体手机"},
# "线程ID":{"telephone":"放入的具体手机"},
# "线程ID":{"telephone":"放入的具体手机"}
#
# }
def set_telephone(telephone):
# 获取线程ID
thread_id = threading.get_ident()
global_goods_dict[thread_id] = {}
global_goods_dict[thread_id]["telephone"] = telephone
print(threading.current_thread().name + " 放入的手机是", telephone)
time.sleep(1)
get_telephone()
def get_telephone():
thread_id = threading.get_ident()
print(threading.current_thread().name + " 取出的手机是", global_goods_dict[thread_id]["telephone"])
if __name__ == '__main__':
for i in range(3):
thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
thread.start()
运行结果同上,这里定义了一个全局的字典global_goods_dict,字典的键盘是线程ID,这就保证了每个线程只能取到自己设置的数据。字典的值同样是一个字典。这是因为一个线程的要存的值可能不止一个。这里的global_goods_dict[thread_id]["telephone"] = telephone
就等价于上例中的local.telephone = telephone
。这样使用虽然能达到效果,但是使用起来还是有点繁琐。那么能不能想local()函数那样使用起来丝滑呢。
4. 简化代码操作,进一步模拟实现local()函数
我们可以将全局的global_goods_dict字典用一个类封装到一个类中。让该类在自动的设置值
class MyBox:
box = {}
def __setattr__(self, key, value):
thread_id = threading.get_ident()
# 单元格已存在
if thread_id in MyBox.box:
MyBox.box[thread_id][key] = value
else:
MyBox.box[thread_id] = {key: value}
def __getattr__(self, item):
thread_id = threading.get_ident()
return MyBox.box[thread_id][item]
def set_telephone(telephone):
myBox.telephone = telephone
print(threading.current_thread().name + " 放入的手机是", myBox.telephone + "\n")
time.sleep(1)
get_telephone()
def get_telephone():
print(threading.current_thread().name + " 取出的手机是", myBox.telephone + "\n")
if __name__ == '__main__':
myBox = MyBox()
for i in range(3):
thread = threading.Thread(target=set_telephone, name='学生' + str(i), args=('手机' + str(i),))
thread.start()
运行结果同上。这里通过MyBox类封装了一个名为box的字典。该字典的键是当前线程ID,值是赋值的变量名以及值组成的键值对。当执行set_telephone方法的myBox.telephone = telephone
,实际上会调用MyBox的__setattr__
方法,参数key是telephone,参数value是"手机xx"。当调用myBox.telephone时实际上会调用__getattr__
方法,传入的参数item是telephone。取值时首先获取当前线程ID。
来源:https://blog.csdn.net/u014534808/article/details/120106866


猜你喜欢
- 在一个排列中,如果一对数的前后位置与大小顺序相反,即前面的数大于后面的数,那么它们就称为一个逆序。一个排列中逆序的总数就称为这个排列的逆序数
- 本文实例为大家分享了python创建单词词库的具体代码,供大家参考,具体内容如下基本思路:以COCA两万单词表为基础,用python爬取金山
- 本文实例讲述了Python查找最长不包含重复字符的子字符串算法。分享给大家供大家参考,具体如下:题目描述请从字符串中找出一个最长的不包含重复
- 代码如下:<SCRIPT LANGUAGE="JavaScript"> <!-- //说明:这里用了M
- 空接口定义空接口是特殊形式的接口类型,普通的接口都有方法,而空接口没有定义任何方法口,也因此,我们可以说所有类型都至少实现了空接口。type
- 我使用Pytorch进行模型训练时发现真正模型本身对于显存的占用并不明显,但是对应的转换为tensorflow后(权重也进行了转换),发现P
- 使用echart卡在引入包的问题上了。到github下载的js一直引入不了。注意是引入dirt文件夹下的echarts.js把这个文件夹放入
- 本文实例形式讲解了Python3的条件与循环控制语句及其用法,是学习Python所必须掌握的重要知识点,现共享给大家供大家参考。具体如下:一
- 在一个群上看到好几次问到call和apply的作用,function这两个方法的效果大家都很容易理解,但一般很难让人深刻地理解使用它们的时机
- 实例如下:/** * 数字格式转换成千分位 *@param{Object}num */function commafy(num){ &nbs
- 今天突然想起做一个当鼠标经过<a/>时,会发出声音Js代码如下: <script type="text
- JS动态加载CSS 在可换主题的界面中具有很重要的意义,用户可以根据自己的浏览习惯选择自己喜欢的页面显示方式,下面详细说明。希望下面的方法对
- 本文实例讲述了基于进程内通讯的python聊天室实现方法。分享给大家供大家参考。具体如下:#!/usr/bin/env python# Ad
- 有了Web框架和ORM框架,我们就可以开始装配App了。通常,一个Web App在运行时都需要读取配置文件,比如数据库的用户名、口令等,在不
- 今天给大伙分享一下 Python 爬虫的教程,这次主要涉及到的是关于某 APP 的逆向分析并抓取数据,关于 APP 的反爬会麻烦一些,比如
- pyecharts介绍pyecharts是python与echarts链接,一个用于生成Echarts图标的第三方库,pyecharts分为
- 程序如下:<%Function GetEmploymentStatusListDim dd = Ap
- 文章介绍内容以Python 3.x版本为主一、for循环语句程序一般情况下都是按顺序执行代码,在代码执行过程中,会有复杂的语句,这个时候循环
- 1、问题描述在项目开发中,当我们通过npm run build打包之后将文件放在服务器上时通常会出现图片失效问题,控制台中提示某个图片没有找
- 一,开窗函数的语法开窗函数的语法为:over(partition by 列名1 order by 列名2 ),括号中的两个关键词partit