python并发首选进程,但偶尔有场景进程无法搞定,比如有些变量是无法序列化的,就无法使用工具包manager()的工具类进行共享。如果自己实现新的共享方法,可能开发量较大,且质量难以保证。此时可考虑用线程处理,规避进程的变量共享难题,而且实际场景中,IO大概率都是瓶颈,所以使用线程其实也的确有些优势。个人而言,选择进程和线程较为重视的安全性,进程数据隔离较好,互不干扰。其次就是公用数据占比,如果大多数数据都需公用,那么线程也会比进程更佳,避免了进程较多的数据共享问题。
线程而言,难点数据一致性,
哪些共享,哪些不共享
使用线程,大概率出现的情况,本以为没共享,实际共享了。由于(以为)没共享,所以没做同步处理,导致最后数据一团糟。
参考代码:
1 | # coding=utf-8 |
执行结果:
1 | /home/john/anaconda3/bin/python3 /home/john/PYTHON/scripts/concurrent/threadShare.py |
结果解析:
共享数据的同步(参考博文:python进阶06并发之二技术点关键词)
最简单做法,凡是会在多个线程中修改的共享对象(变量),都加锁。这样可能会有部分锁多加了,但绝对好过不加,毕竟多加锁无非导致效率低下(也可能导致死锁),而一旦该加的没有加,则会导致数据错误,二者孰轻孰重很清楚。建议多了解下”原子操作“,如果不熟悉,可以按照先加锁,再删锁的思路,将原子操作的锁删掉即可(业务逻辑开发阶段,哪些会在多个线程被修改,是很难想全面的。所以一般是先开发,实现业务逻辑思路,再找共享变量,尽可能缩小临界区间,最后再上锁)。这样一方面保险,另一方面也避免了过多锁带来的低效问题。
thread完整版和简单版的关系
1 | class Thread: |
线程本身就有局部变量,为何还需要ThreadLocal?
ThreadLocal例子
1 | import threading |
网上没有查询到有效资料,说明个人理解吧,
首先,如果你的线程采用了完整模式书写(定义class继承thread),则的确不需要使用ThreadLocal,其init内完全可以定义对象自身的私有变量(list等引用型入参,可通过deepcopy复制出私有的一份).
如果你想采用简洁模式, threading.Thread(target= process_thread, args=xx),那么其实是没有定义私有变量的地方的(也不是完全没有,如果是int,str等,本来就是形参,如果是list()则会共享)
举例:
1 | ############# 线程的变量共享(short mode) ############# |
结果:(和前面相同)
1 | (global) gnum 线程(Thread-1) id:94051294298272 num=101 |
可见,对于单个函数的线程,其实没必要使用threadLocal
那么那种情况需要使用呢?
1 | global_dict = {} |
对于存在调用子函数,且函数之间存在参数传递的情况才需要使用threadLocal
同时,如果本身thread使用的就是完整模式的thread了,那么由于本身的self.xx已经是局部变量了,所以也不需要使用threadLocal进行中转保存.
综上所述,其实threadLocal的使用场景是比较有限的,必须是thread简洁模式下,存在函数调用和传参的情况下在有必要使用。
类锁还是实例锁?
由于锁和临界区是对应的(作为临界变量,临界区的保镖),如果临界变量(区)是类级别信息(比如统计类实例个数),就用类锁,否则就是实例锁。
阻塞式io中,cpu分配时间片给阻塞线程么
运行态—wait/阻塞io-→阻塞态
运行态——-调度——–→就绪态
就绪态——-调度——–→运行态
阻塞态—信号/io返回-→就绪态
所以不占用时间片。
sleep()和wait()这两个函数被调用之后线程都应该放弃执行权,不同的是sleep()不释放锁而wait()的话是释放锁。直白的意思是一个线程调用Sleep()之后进入了阻塞状态中的其他阻塞,它的意思就是当sleep()状态超时、join()等待线程终止或者超时,线程重新转入可运行(runnable)状态。而Wait()是不同的在释放执行权之后wait也把锁释放了进入了线程等待阻塞,它要运行的话还是要和其他的线程去竞争锁,之后才可以获得执行权。
多线程中,target为实例方法,可访问哪些变量的测试
举例:
get_face_result中可以访问self里面的哪些资源?在1处(thread声明后,start前)和2处(start后)定义的变量可以访问么?
主要疑惑:target=get_face_result,如果get_face_result看作普通函数,那么由于不存在全局变量,所以所有参数都应该从args传入,
问题就是在get_face_result不是普通函数,不确定python是否会把self.里的变量传递到get_face_result内部(也就是self.里的实例变量看作 self.get_face_result的全局变量)
结论:以thraed.start为界,start之前一定可以访问,start之后是否可以访问,视主线程和子线程执行速度,可能可以,可能不行
所以:1处的代码,子线程可以访问,2处的代码,由子线程执行速度(访问2处赋值的变量的时间),和父线程执行速度决定,如果父线程速度快,那么2处的(子线程)也可以访问,否则,(子线程)无法访问。
测试步骤
测试01:如上图
结果:
1 | 1 |
结论:start后变量,如果有足够时间差,子线程就可以访问
测试02:
结论:start后变量,没有足够时间差,子线程无法访问
测试04:引用型变量会如何
结论不变,start后,父线程子线程先后顺序影响了变量赋值
Condition和Event
参考:
Python多线程—线程同步(Lock/RLock、Condition、queue、Event):https://blog.csdn.net/Lesour/article/details/88808139
参考
python ThreadLocal
深入理解Python中的ThreadLocal变量(上)
Python中ThreadLocal的理解与使用
在阻塞式io中,如果一个线程在等待io操作,那么cpu还会分配时间片给该线程吗?
Python多线程—线程同步(Lock/RLock、Condition、queue、Event):https://blog.csdn.net/Lesour/article/details/88808139
python笔记11-多线程之Condition(条件变量)(notify,wait锁传递,细节控制):https://www.cnblogs.com/yoyoketang/p/8337118.html
python3是如何使用线程的(Event与Condition)(在python cookbook中建议event作为一次性事件使用,Condition可以进行多次通知,并通知不同数量的线程):https://blog.csdn.net/qq_34392457/article/details/108319357
python进阶系列
python进阶01偏函数
python进阶02yield
python进阶03UnboundLocalError和NameError错误
python进阶04IO的同步异步,阻塞非阻塞
python进阶04IO的同步异步,阻塞非阻塞
python进阶05并发之一基本概念
python进阶05并发之一基本概念
python进阶06并发之二技术点关键词
python进阶07并发之三其他问题
python进阶08并发之四map, apply, map_async, apply_async差异
python进阶09并发之五生产者消费者
python进阶10并发之六并行化改造
python进阶11并发之七多种并发方式的效率测试
python进阶12并发之八多线程与数据同步
python进阶13并发之九多进程和数据共享
python进阶14变量作用域LEGB
python进阶15多继承与Mixin
python进阶16炫技巧
python进阶17正则表达式
python进阶18垃圾回收GC
python进阶19装饰器和闭包
python进阶20之actor
python进阶21再识单例模式