
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对象的一个量,并不是线程本身提供了取消的能力
来源:CSDN
作者:PX小葵
链接:https://blog.csdn.net/qq_42227818/article/details/103628188