Python threading Local()函数用法案例详解

目录
  • 前言
  • local() 函数是什么?
  • local()函数如何用?
    • 1. 不做标记,不做隔离
    • 2.使用local()函数加以控制
    • 3. 模拟实现local()的功能,创建一个箱子
    • 4. 简化代码操作,进一步模拟实现local()函数
  • 总结

前言

当多线程访问同一个公共资源时,如果涉及到修改该公共资源的操作就可能会出现由于数据不同步导致的线程安全问题。一般情况下我们可以通过给公共资源加互斥锁的方式来处理该问题。

当然,除非必须将多线程使用的资源设置为公共资源的情况。如果一个资源不需要在多个线程之间共享。我们也可以使用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。

总结

本文从实际例子出发详细介绍了threading模块的local()函数的使用。

到此这篇关于Python threading Local()函数用法案例详解的文章就介绍到这了,更多相关Python threading Local()函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • python threading模块的使用指南

    1. threding模块创建线程对象 接上述案例,我们可以利用程序阻塞的时间让程序执行后面的任务,可以用多线程的方式去实现.对应的需要我们借助threading模块去实现: 如下所示 import time import threading def work(): """只有函数对象才能佈田名线积""" print('5.洗茶杯: 1min ' ) time.sleep(1) print('6.放茶叶: 1min ' ) time.sleep(

  • Python中threading库实现线程锁与释放锁

    控制资源访问 前文提到threading库在多线程时,对同一资源的访问容易导致破坏与丢失数据.为了保证安全的访问一个资源对象,我们需要创建锁. 示例如下: import threading import time class AddThread(): def __init__(self, start=0): self.lock = threading.Lock() self.value = start def increment(self): print("Wait Lock") se

  • python语言线程标准库threading.local解读总结

    本段源码可以学习的地方: 1. 考虑到效率问题,可以通过上下文的机制,在属性被访问的时候临时构建: 2. 可以重写一些魔术方法,比如 __new__ 方法,在调用 object.__new__(cls) 前后进行属性的一些小设置: 3. 在本库中使用的重写魔术方法,上下文这两种基础之上,我们可以想到函数装饰器,类装饰器,异常捕获,以及两种上下文的结构: 灵活运用这些手法,可以让我们在代码架构上更上一层,能够更加省时省力. from weakref import ref # ref用在了构造大字典

  • python中threading和queue库实现多线程编程

    摘要 本文主要介绍了利用python的 threading和queue库实现多线程编程,并封装为一个类,方便读者嵌入自己的业务逻辑.最后以机器学习的一个超参数选择为例进行演示. 多线程实现逻辑封装 实例化该类后,在.object_func函数中加入自己的业务逻辑,再调用.run方法即可. # -*- coding: utf-8 -*- # @Time : 2021/2/4 14:36 # @Author : CyrusMay WJ # @FileName: run.py # @Software:

  • Python threading.local代码实例及原理解析

    Python的线程操作在旧版本中使用的是thread模块,在Python27和Python3中引入了threading模块,同时thread模块在Python3中改名为_thread模块,threading模块相较于thread模块,对于线程的操作更加的丰富,而且threading模块本身也是相当于对thread模块的进一步封装而成,thread模块有的功能threading模块也都有,所以涉及到对线程的操作,推荐使用threading模块. threading模块中包含了关于线程操作的丰富功能

  • Python threading Local()函数用法案例详解

    目录 前言 local() 函数是什么? local()函数如何用? 1. 不做标记,不做隔离 2.使用local()函数加以控制 3. 模拟实现local()的功能,创建一个箱子 4. 简化代码操作,进一步模拟实现local()函数 总结 前言 当多线程访问同一个公共资源时,如果涉及到修改该公共资源的操作就可能会出现由于数据不同步导致的线程安全问题.一般情况下我们可以通过给公共资源加互斥锁的方式来处理该问题. 当然,除非必须将多线程使用的资源设置为公共资源的情况.如果一个资源不需要在多个线程之

  • Python文件操作函数用法实例详解

    这篇文章主要介绍了Python文件操作函数用法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 字符编码 二进制和字符之间的转换过程 --> 字符编码 ascii,gbk,shit,fuck 每个国家都有自己的编码方式 美国电脑内存中的编码方式为ascii ; 中国电脑内存中的编码方式为gbk , 美国电脑无法识别中国电脑写的程序 , 中国电脑无法识别美国电脑写的程序 现在硬盘中躺着 ascii/gbk/shit/fuck 编码的文件,

  • C++ seekg函数用法案例详解

    C++ seekg函数用法详解 很多时候用户可能会这样操作,打开一个文件,处理其中的所有数据,然后将文件倒回到开头,再次对它进行处理,但是这可能有点不同.例如,用户可能会要求程序在数据库中搜索某种类型的所有记录,当这些记录被找到时,用户又可能希望在数据库中搜索其他类型的所有记录. 文件流类提供了许多不同的成员函数,可以用来在文件中移动.其中的一个方法如下: seekg(offset, place); 这个输入流类的成员函数的名字 seekg 由两部分组成.首先是 seek(寻找)到文件中的某个地

  • StretchBlt函数和BitBlt函数用法案例详解

    StretchBlt和BitBlt都用在双缓冲视图中,用来显示一幅图像 一.StretchBlt 函数从源矩形中复制一个位图到目标矩形,必要时按目标设备设置的模式进行图像的拉伸或压缩.也即是将内存中的位图拷贝到屏幕上,并且可以根据屏幕画图区的大小来进行伸缩,适应响应的屏幕(或图像控件) BOOL StretchBlt( int x, int y, int nWidth, int nHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, in

  • C++ assert()函数用法案例详解

    1. 简介 assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行. 原型定义: #include <assert.h> void assert( int expression ); assert的作用是先计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行.请看下面的程序清单badptr.c: #include <stdio.h> #incl

  • C++ atoi()函数用法案例详解

    目录 1 功能 2 格式 3 注意事项 3.1 关于参数的注意事项 3.2 关于返回值的注意事项 3.3 判断转换是否成功 4 宽字符的转换 1 功能 atoi()函数将数字格式的字符串转换为整数类型.例如,将字符串"12345"转换成数字12345. 2 格式 该函数的格式为 int atoi(const char* str) 其中,参数str是要转换的字符串,返回值是转换后的整数. 3 注意事项 3.1 关于参数的注意事项 在"2 格式"中提到,atoi()函数

  • C++ 转换函数用法案例详解

    1.标准数据之间会进行隐式类型安全转换,规则如下:  在这里主要探讨c++中类类型与普通类型的转换: 1.类类型转换普通类型 class Fraction { public: Fraction(int num,int den=1); ~Fraction(); //转换函数 /* 转换函数语法规则: operator Type() { Type ret ; ........ return ret; } */ operator double() const { return (double)(m_N

  • Python基础之函数用法实例详解

    本文以实例形式较为详细的讲述了Python函数的用法,对于初学Python的朋友有不错的借鉴价值.分享给大家供大家参考之用.具体分析如下: 通常来说,Python的函数是由一个新的语句编写,即def,def是可执行的语句--函数并不存在,直到Python运行了def后才存在. 函数是通过赋值传递的,参数通过赋值传递给函数 def语句将创建一个函数对象并将其赋值给一个变量名,def语句的一般格式如下: def <name>(arg1,arg2,arg3,--,argN): <stateme

  • Python中return用法案例详解

    python中return的用法 1.return语句就是把执行结果返回到调用的地方,并把程序的控制权一起返回 程序运行到所遇到的第一个return即返回(退出def块),不会再运行第二个return. 例如: def haha(x,y): if x==y: return x,y print(haha(1,1)) 已改正: 结果:这种return传参会返回元组(1, 1) 2.但是也并不意味着一个函数体中只能有一个return 语句,例如: def test_return(x): if x >

  • python scatter函数用法实例详解

    这篇文章主要介绍了python scatter函数用法实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 函数功能:寻找变量之间的关系. 调用签名:plt.scatter(x, y, c="b", label="scatter figure") x: x轴上的数值 y: y轴上的数值 c:散点图中的标记的颜色 label:标记图形内容的标签文本 代码实现: import matplotlib.pyplot as

随机推荐