线程
线程
一:理论基础
线程比进程重要的多,要重点的掌握.GIL完全不影响python的多线程,因为IO时间相对于cpu的计算时间是很长的,时间复用没有问题.
GIL锁是cpython解释器独有的,因为他的垃圾回收机制.
jpython是把python解释为java代码去执行的,java的GC不是用的计数器做的,是根标记.
硬件越来越牛逼,算的是很快的,只是IO慢,所以GIL对python多进程影响不大.
实在想用多核并行,那么就开对应数目的进程,然后进程里面开多进程,但是只是理想,web框架你全部是多线程,你能充分利用一个进程把cpu的效率提上来已经很牛逼了.
遇到计算密集型就换多进程,最好分布式.
python和java一样GC都采用引用计数法和分代回收机制,每执行一次代码,计数器对每个变量的引用都会计数,一旦后面没有继续使用的地方就会回收掉,而GC就是工作在一个线程里.
而其他的线程并发里面引用了相同的变量,这时候计数器无法精确的计数,数据是不安全的,所以才出现了GIL锁,可以看做是串行的,导致无论开多少线程,只要被cpu执行了同一时间只有一个线程被执行
创建进程切换进程销毁消耗大.
两个程序分别做两件事,开2个进程,看视频,和qq聊天.
一个程序做多件事情,播放器里面下载着2个电影,还看着一个电影.启动三个进程完成这一件事理论上操作上都没有问题.但是开销十分大.
任何一个进程里面都至少有一个线程,它只负责执行代码,而资源分配都是进程完成的,至于局部ns,全局ns都是python解释器放在了进程申请的不同内存里面,线程不参与,线程只负责执行代码,还有找数据
进程负责资源的分配,划分不同的区域,放置不同的数据,线程负责执行代码,cpu调度的最小单位是线程.
比较
创建进程需要开许多的资源,创建线程的资源和时间远远小于进程,运行函数都要开空间呢,开空间并不是贬义的.
切换线程要记录执行的状态,执行到的位置,也需要一些内存开销,然后才切换线程.
python的线程特殊,也有可能用到进程
进程数据隔离,线程数据共享
二:Thread
完全仿照的Process,但是开启一个进程非常快,后面紧挨着的print代码没有执行,进程就已经开启执行了.只是包不同 from threading import Thread
多线程没有if __name__=="__main__":这个必要条件,因为进程共享数据,不用重开内存拷贝数据和代码,不用import一遍主进程代码
- 主线程结束意味着主进程也就结束了,子线程也依托于主进程
- 主线程会等待所有的子进程结束后才结束.
- start()方法里面封装了c语言写的与os交互的开启进程的方法,然后才调用的run方法,所以你不可以重写run方法
- from threading import current)thread ,current_thread()这个方法可以获取当前的进程号
- 还有此包里面的enumerate()和activate_count()查看所有线程和数量,后者就是算的前者的数目
- 子线程只能执行完自己结束,没有terminate()
- 守护线程等待所有线程结束才结束,除了守护主线程还有守护子线程
- 先非守护线程结束,主线程结束,主进程结束,守护线程结束.
三:锁
dis模块,可以看到一条指令或者一个函数执行了多少cpu指令
就像java里面操作之间有间隙,看着写了一条代码,但是底层却执行了很多cpu指令,因此为了保证多个指令的原子性,必须加锁
import dis a = 0 def func(): global a a+=1#只要涉及到复合运算的都是线程不安全的. print(dis.dis(func))
6 0 LOAD_GLOBAL 0 (a) 2 LOAD_CONST 1 (1) 4 INPLACE_ADD 6 STORE_GLOBAL 0 (a)#只有有这一句,就会很大的可能导致线程不安全 8 LOAD_CONST 0 (None) 10 RETURN_VALUE None
尽管有一个GIL锁,但是并不能保证数据安全,多个线程 解释器中的GIL锁只能保证同一时间有一个线程被解释器放出去给cpu执行,但是不能保证全部线程代码的原子性,
就是说Cpython解释器同一时间点只能翻译一个线程的代码.
import dis from threading import Thread,Lock a = 0 def func(lock): global a for i in range(20000): with lock: a+=1 def func2(lock): global a for i in range(20000): with lock: a-=1 if __name__ == "__main__": lock = Lock() t1 = Thread(name = "th1",target=func,args=(lock,)) t2 = Thread(name = "th2",target=func2,args=(lock,)) t1.start() t2.start() t1.join() t2.join() print(a)
一定在主线程join在输出a,否则拿到的不是最终值,因为是异步非阻塞的,加了之后才会阻塞等待.
GIL锁你无法控制,阻塞时候自动交出去,时间片到了也自动交出去.
而创建的Lock是互斥锁,需要手动控制
死锁:
1多把锁
2多把锁交替使用
递归锁:可以在同一个线程里面连续acquire()多次不会阻塞,
from threading import RLock
好在多次acquire不会阻塞,缺点在占用资源多效率低
之所以递归锁没出现死锁是因为只有一把锁,如果创建多个递归锁也是会死锁
四:线程队列
import queue并发安全容器,不用加锁,只能线程通信,进程不好用
五:池concurrent.futures
避免频繁的开启和销毁进程和线程来达到并发,创建后放到池中,用到就拿,用完就归还,不销毁,一直待命状态

浙公网安备 33010602011771号