【2020Python修炼记】python并发编程(五)多线程-应用部分(python 线程并发)

网友投稿 791 2022-09-20

【2020Python修炼记】python并发编程(五)多线程-应用部分(python 线程并发)

【2020Python修炼记】python并发编程(五)多线程-应用部分(python 线程并发)

一、 threading模块介绍

进程的multiprocess模块,完全模仿了threading模块的接口,二者在使用层面,有很大的相似性,此处省略好多字。。

multiprocess模块用法回顾:https://cnblogs.com/bigorangecc/p/12759151.html

官网链接:https://docs.python.org/3/library/threading.html?highlight=threading#

二 、开启线程的两种方式

1、实例化类创建对象

2、类的继承

三 、在一个进程下开启多个线程与在一个进程下开启多个子进程的区别

1、比较开启速度

2、pid

3、同一进程内的线程之间共享进程内的数据

四、 线程相关的其他方法

1、thread实例对象的方法:

1、isAlive(): 返回线程是否活动的,存活的。

2、getName(): 返回线程名。

3、setName(): 设置线程名。

2、threading模块提供的一些方法:

1、threading.currentThread()  

返回当前的线程变量。

2、threading.enumerate()

返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。

3、threading.activeCount()

返回正在运行的线程数量,与 len(threading.enumerate()) 有相同的结果。

3、join()的使用——主线程等待子线程结束

五、守护线程

1、强调

无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁

需要强调的是:运行完毕(真正的任务已经做完,但还没有下班) 并非 终止运行(任务已经完成,收拾好东西下班了)

#1.对主进程来说,运行完毕指的是主进程代码运行完毕

#2.对主线程来说,运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕

详细解释:

#1 主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),

     然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵尸进程),才会结束运行。

#2 主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。

    因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程必须保证非守护线程都运行完毕后才能结束。

2、两颗栗子

六、Python GIL (Global Interpreter Lock)

1、强调

# 在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势.

# GIL 并不是python的特性,它是在实现python解释器(cpython)时所引入的一个概念,

    即 GIL 是 python解释器(cpython)的特性。

2、GIL

GIL 本质就是一把互斥锁,既然是互斥锁,所有互斥锁的本质都一样,都是将并发运行变成串行,以此来控制同一时间内共享数据只能被一个任务所修改,进而保证数据安全。有了GIL的存在,同一时刻同一进程中只有一个线程被执行。

可以肯定的一点是:保护不同的数据的安全,就应该加不同的锁。

参考阅读:

https://cnblogs.com/linhaifeng/articles/7449853.html

八、同步锁

1、三个需要注意的点:

#1.GIL & Lock

线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,

其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来。

(GIL锁 和 互斥锁Lock 都抢到 ,才是王道)

#2. join() & Lock 

join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,

要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

#3. GIL与互斥锁的经典分析,见本小节末---必看

2、Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock?

一个共识 —— 锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据

一个结论 —— 保护不同的数据就应该加不同的锁。

明朗答案 —— GIL 与Lock是两把锁,保护的数据不一样,

      前者 GIL 是解释器级别的(当然保护的就是解释器级别的数据,比如垃圾回收的数据),

      后者Lock 是保护用户自己开发的应用程序的数据,很明显GIL不负责这件事,只能用户自定义加锁处理,即Lock

3、GIL锁  VS  互斥锁Lock ——代码栗子

GIL锁与互斥锁综合分析:

#1.100个线程去抢GIL锁,即抢执行权限

#2. 肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()

#3. 极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL

#4.直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程

4、让并发变成串行——互斥锁 和 join()的区别

join是等待所有,即整体串行,而锁只是锁住修改共享数据的部分,即部分串行,

要想保证数据安全的根本原理在于让并发变成串行,join与互斥锁都可以实现,毫无疑问,互斥锁的部分串行效率要更高

九、死锁现象与递归锁——进程和线程都有死锁现象与递归锁

所谓死锁:

是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程,如下就是死锁:

解决方法——递归锁

在Python中为了支持在同一线程中多次请求同一资源,python提供了可重入锁RLock。

这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,其他的线程才能获得资源。上面的例子如果使用RLock代替Lock,则不会发生死锁:

mutexA=mutexB=threading.RLock()

#一个线程拿到锁,counter加1,该线程内又碰到加锁的情况,则counter继续加1,这期间所有其他线程都只能等待,等待该线程释放所有锁,即counter递减到0为止

十、线程 的类 queue

1、线程queue

queue队列 :使用 import queue,用法与进程Queue一样

     (注意区分——进程的Queue,进程的队列首字母为大写Q,线程的为小写q

                   进程中的导入方法:from multiprocessing import Process,Queue)

2、主要用法

class queue.Queue(maxsize=0)   #先进先出

class queue.LifoQueue(maxsize=0)    #last in fisrt out

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Solaris10 修改主机名和IP地址
下一篇:初探 Unix 操作系统
相关文章

 发表评论

暂时没有评论,来抢沙发吧~