2019/12/19 03-thread-local和Timer

不羁岁月 提交于 2019-12-21 02:32:32

在这里插入图片描述

threading.local

先看一下简单程序
asctime 时间,threadname线程明,message消息,
logging在info级别,默认实在warning级别

现在里面写的就是未来的信息了,msg
在这里插入图片描述
x就是上面的message消息
这5个线程用的都是局部的变量,这5个worker线程执行的时候互不干扰,每一个函数的局部变量x都是在这个线程上的栈压进去的,这些局部变量互不干扰。各自在各自的线程上

在这里插入图片描述
最后都是一样的
在这里插入图片描述
做一个猜测,只要你使用的局部变量,就是很安全的,只在各自的当前环境使用,只在线程这个函数的栈上使用,虽然这个对象可能在堆上创建的,但是只归这个函数使用,互不干扰在这里插入图片描述
试试一个全局变量,全局的边界是当前模块,global x 告诉x是全局作用域去找
在这里插入图片描述
再执行一次在这里插入图片描述在这里插入图片描述
前面线程加完,全局x刚用完,后面线程来的时候,紧接着代表全局变量直接加了,最后都是500次的加,次数是不会少的,该加多少是多少
在这里插入图片描述
如果要每次加都得到100,就需要使用局部变量,不能使用none local(none local 是在本地作用域不定义,在外层非global的区域定义,这里使用none local就到全局里去了在这里插入图片描述
现在添加一个类,用一个全局的实例来解决a.x+1在这里插入图片描述
在这里插入图片描述
如果想每次得到的结果是100,但是又想用全局变量,该如何做,python提供了local这样的一个类
定义了__slots__就是两个属性
在这里插入图片描述在这里插入图片描述
这个类在多线程的时候用处比较大,每个线程自己玩自己的,但是用的还是同一个全局对象。
a=threading.local用这个类来创建一个实例,每个线程进来的时候,把初始值给上
在这里插入图片描述每次执行都不一样在这里插入图片描述
偶尔看到一次500
在这里插入图片描述在这里插入图片描述
原因是有一个线程刚执行一小会,算了一会,下一个线程启动就清0在这里插入图片描述
换成这样,结果可预期,一定是100在这里插入图片描述
阅读源码试试,如何实现
在这里插入图片描述在这里插入图片描述
内部就需要用到字典来查询对于iD的线程,线程ID是唯一的,内存地址也是唯一的,但是线程ID号有可能被回收利用,但是可以保证在运行的时候,这两个是唯一的

创建一个实例,看到self,就能明白,是当前这个类的实例本身
在这里插入图片描述
这一句话要产生一个local实例
在这里插入图片描述
创建一个对象加锁,把传进来的参数保存起来,(args,kw)前面元组后面字典在这里插入图片描述
每创建一个local实例,是所有线程共用的,现在还在右边,没有返回在这里插入图片描述
创建一个字典在这里插入图片描述
self,创建一个实例,为实例增加属性 _local_impl,__setattr__这个方法调用object方法,为self这个对象动态增加属性,跟impl关联,相当于添加一个引用,这个imp1后面create_dict创建字典在这里插入图片描述
在local的时候创建了一个实例,为这个实例动态塞入了local_impl属性,这个local实例有两个属性,lmp1和dict,有这个字典就可以使用实例self.XXX在这里插入图片描述
slots就是约定有多少个属性,self是当前locallimpl的实例,每创建一次,这个key肯定不同,因为实例的id没办法
在这里插入图片描述
在这里插入图片描述
一个线程有两个属性是在当前运行环境是唯一的,线程ID,运行线程对象的内存地址,这里拿的是内存地址作为key是int类型 的,后面是等于一个线程的引用,和thread-local的字典构成的元组
self。dicts这个字典是用来装一个个线程和线程保存的数据(线程本身和线程相关字典)做一下关联
在这里插入图片描述
local()就会创建一个thread。local的对象,这个对象会生成locallmpl实例,把它作为增加的属性加入进来,并且在这个对象上创建一个dicts在这里插入图片描述对象上创建一个dicts,因为是大字典套了小字典,是个二维字典,小字典跟线程相关在这里插入图片描述
到达a.x该如何,a是类的实例,属性搜索顺序是属性的字典,getattr属性找不到,get attribute(不管找什么,先找这个)在这里插入图片描述get attribute(不管找什么,先找这个,就被截住了,被local)a是local的实例,
a.x访问__getattribute__(self,name=‘x’)
在这里插入图片描述
真正的问题跟他相关在这里插入图片描述
前面的local没有dict属性
在这里插入图片描述
不能直接调用这个,调用不了
在这里插入图片描述
使用with代表,这个实例,函数,还是类,一定支持上下文管理在这里插入图片描述
一个类实现上下文管理,实现enter 和exit,还有一种方法,就是函数,必须要用装饰器@contextmanager,以yield为界一分为2,yield之上当enter(yield的返回值作为enter的返回值)yield之后作为exit退出的东西

在这里插入图片描述

利用当前线程get字典
在这里插入图片描述
下面的self是local对象,为这个对象临时增加属性,dct小字典,也就是当前线程是谁,找到大字典里面的小字典,把local对象 的字典替换成跟当前线程key对应的小字典
在这里插入图片描述
看一下create_dict,怎么创建出一个字典的,这条语句要执行一定会在线程中
在这里插入图片描述
这条语句刚创建是在主线程中
在这里插入图片描述
localdict是局部变量,idt 得到线程id(thread)在这里插入图片描述
为当前线程动态在他字典里增加key,在这里插入图片描述
这个key就在每个线程里面都把实例绑定了
在这里插入图片描述
关键是下面,右边是封装成元组
在这里插入图片描述
wrthread,对原有对象thread进行一个包装ref(thread,thread_deleted)在这里插入图片描述在这里插入图片描述
这个就是字典套字典,为每个线程开辟了小灶,小字典是套在大字典里的,每个线程都会有自己的字典在这里插入图片描述在这里插入图片描述
a.x其实已经换线程了,换线程就需要重新装配字典
在这里插入图片描述
get拿到后,try如果出错(如果当前线程从来没创建自己的小字典)在这里插入图片描述
就使用create_dict,为当前线程创建自己的内部小字典在这里插入图片描述
没字典就创建,把字典装配到local实例上去
在这里插入图片描述
装配到对应的__dict__属性上去在这里插入图片描述
a。x=0归__settter__管在这里插入图片描述
帮你把字典装配完成
在这里插入图片描述
patch要么发现没有小字典就创建一个,要么没有字典就装配上,给你用在这里插入图片描述
用有两种方式,要么读取,要么就是写入,都是在给字典做写入和读取在这里插入图片描述
不同线程切换这个字典,也就是local实例在不同的线程下,dict一直在换
在这里插入图片描述
a.X=0发现当前线程没有字典,会创建一个字典并且装配起来,到达seatter,将kv对塞进去,k是x,v=0
在这里插入图片描述
做这个事情的时候线程来回切,每个线程用自己的字典,不会干扰,只要线程发生切换,就会跟自己实例跟当前线程相关的字典装配起来,开始a.x +=1,各用各的字典,不会发生争抢,最后打印调用自己的小字典,最后打印的都是100在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述在这里插入图片描述
这是对当前主线程创建这样一个属性
在这里插入图片描述在这里插入图片描述
这样打相当于开启一个新线程来打印了,x,c体系、,都是全局的,可以访问
ctx.x(就会触发get attribute with patch,就看你当前线程有没有字典,没有就创建一个,创建一后就装配上,属性是空的,报错)
在这里插入图片描述
本质上,threading.local在实例运行中,就从大字典找到当前线程相关键值对中的字典,覆盖threading.local实例的__dict__,每一次都在换每一个线程所对应的字典,这样就做到线程安全了
在这里插入图片描述
现在安全使用线程的数据,有两种方案,第一种方案,让变量变成局部变量(最常用,最简单的方法),如果非要使用同一个对象,这个对象只能在线程外面创建。就需要把全区变量变成threading.local,这样就可以使用全局变量达到线程安全的方式,这样达到的结果是预期的结果

线程不安全非常不好,要避免出现

Timer 是一个类

在这里插入图片描述
在这里插入图片描述
是Thread的子类,thread有什么能力,它也基本都有
在这里插入图片描述
interval间隔时间,既然是thread子类,start就不能再启动第二次了,意思是间隔多少秒之后要执行的函数,function就是target,agrs,kwargs就是为这个线程函数function传参的在这里插入图片描述
event事件
在这里插入图片描述
把run方法重写了。,没有调用super,Timer虽然做到延时执行,但是本质上还是启动了线程执行在这里插入图片描述
set意思是之一,原来状态是0,把0变成1有一个转变过程,状态发生了一个变化在这里插入图片描述
等了很久没有从0变1
if 没有set过就是 true
相当于等了多少秒发现没有把开关打开,则执行此函数,现在有了等待时间,相当于整个函数被等待执行了

在这里插入图片描述
照着例子来使用

在这里插入图片描述在这里插入图片描述
主线程先执行,等t
在这里插入图片描述
创建的时候从基类拿了daemon
在这里插入图片描述
有些函数有先后顺序执行,有需要可以谁当几秒,几秒
加了cancel直接结束了
在这里插入图片描述
cancel放上面也是直接结束
在这里插入图片描述
cancel至1了,就不会等了在这里插入图片描述
is_set代表是否已经至1了
在这里插入图片描述
所谓的cancel就是制止开关的变化,再真正执行函数前,允许你cancel
在这里插入图片描述
start归Thread管,创建一个真正线程出来,这个线程准备run的时候
在这里插入图片描述
run就要看不看等不等
在这里插入图片描述
cancel之后,start依然是要创建线程的,但是创建线程后去调用run方法,run方法里面也不用等,不进判断,is_set,就直接三句话退出(线程想让执行的东西根本没执行,因为 if 语句进不去)
在这里插入图片描述
cancel放satrt后面就不一样了,会等着你至1,卡在线程中,因为线程已经start存在了,然后主线程下面的cancel,一下就不等了,直接退出
在这里插入图片描述
只要function没有执行,都来得及cancel
在这里插入图片描述在这里插入图片描述
Timer本质上是Thread子类,就是线程类,具有线程的能力和特征,重写了run方法,添加了cancel,可以延时执行一段时间,才会真正调用目标函数,在这之前都可以cancel,cancel的本质是用event类来实现的,看似取消线程,实际不是,实际是改变了event对象的一个量,并不是线程本身提供了取消的能力
在这里插入图片描述

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!