Loading

线程

线程

一:理论基础

线程比进程重要的多,要重点的掌握.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一遍主进程代码

  1. 主线程结束意味着主进程也就结束了,子线程也依托于主进程
  2. 主线程会等待所有的子进程结束后才结束.
  3. start()方法里面封装了c语言写的与os交互的开启进程的方法,然后才调用的run方法,所以你不可以重写run方法
  4. from threading import current)thread ,current_thread()这个方法可以获取当前的进程号
  5. 还有此包里面的enumerate()和activate_count()查看所有线程和数量,后者就是算的前者的数目
  6. 子线程只能执行完自己结束,没有terminate() 
  7. 守护线程等待所有线程结束才结束,除了守护主线程还有守护子线程
    1. 先非守护线程结束,主线程结束,主进程结束,守护线程结束.

三:锁

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)
View Code

一定在主线程join在输出a,否则拿到的不是最终值,因为是异步非阻塞的,加了之后才会阻塞等待.

GIL锁你无法控制,阻塞时候自动交出去,时间片到了也自动交出去.

而创建的Lock是互斥锁,需要手动控制

死锁:

1多把锁

2多把锁交替使用

递归锁:可以在同一个线程里面连续acquire()多次不会阻塞,

from threading import RLock

好在多次acquire不会阻塞,缺点在占用资源多效率低

之所以递归锁没出现死锁是因为只有一把锁,如果创建多个递归锁也是会死锁

 

四:线程队列

import queue并发安全容器,不用加锁,只能线程通信,进程不好用

 

五:池concurrent.futures

避免频繁的开启和销毁进程和线程来达到并发,创建后放到池中,用到就拿,用完就归还,不销毁,一直待命状态

 

posted @ 2019-10-10 09:40  浅忆尘  阅读(81)  评论(0)    收藏  举报