Python入门学习-DAY36-GIL全局解释器锁死锁现象与递归锁信号量Event事件线程queue

Posted LIFE

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python入门学习-DAY36-GIL全局解释器锁死锁现象与递归锁信号量Event事件线程queue相关的知识,希望对你有一定的参考价值。

一、GIL全局解释器锁

1. 什么是GIL全局解释器锁

GIL本质就是一把互斥锁,相当于执行权限

在Cpython解释器下,如果想实现并行可以开启多个进程

2. 为何要有GIL

我们首先要知道,一个多线程是怎么执行的,假设在一个进程中有三个线程,线程中是要运行的代码。

①如果要运行代码,就必须要先获得Cpython解释器的权限才能将代码交由解释器翻译成cpu可以理解的语言

②再将翻译好的代码交由操作系统,由操作系统交给CPU执行运算。

 

由于每个进程内都会存在一把GIL,同一进程内的多个线程,必须抢到GIL之后才能使用Cpython解释器来执行自己的代码

即同一进程下的多个线程无法实现并行,但是可以实现并发

那么我们反过来想一下,如果没有GIL的存在,那么多个线程就变成了并行的,要知道解释器中有一个垃圾回收机制,其实也是一个线程,也变成了并行,就会造成一种情况的发生,对于同一个数据100,可能线程1执行x=100的同时,而垃圾回收执行的是回收100的操作,造成了数据的丢失。

 

为了Cpython解释器的垃圾回收机制的线程安全,就必须使用GIL

3. 如何用GIL

有了GIL,应该如何处理并发

我们有四个任务需要处理,处理方式肯定是要玩出并发的效果,解决方案可以是:

方案一:开启四个进程

方案二:一个进程下,开启四个线程

  单核情况下,分析结果:   

    如果四个任务是计算密集型,没有多核来并行计算,方案一徒增了创建进程的开销,方案二胜   

    如果四个任务是I/O密集型,方案一创建进程的开销大,且进程的切换速度远不如线程,方案二胜 

  多核情况下,分析结果:   

    如果四个任务是计算密集型,多核意味着并行计算,在python中一个进程中同一时刻只有一个线程执行用不上多核,方案一胜   

    如果四个任务是I/O密集型,再多的核也解决不了I/O问题,方案二胜

结论:现在的计算机基本上都是多核,python对于计算密集型的任务开多线程的效率并不能带来多大性能上的提升,甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

计算密集型

技术分享图片
from multiprocessing import Process
from threading import Thread
import os,time

def task():
    res=0
    for i in range(100000000):
        res*=i

if __name__ == __main__:
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(4):
        # p=Process(target=task) #耗时16.226743459701538s
        p=Thread(target=task) #耗时26.44382882118225s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(run time is %s %(stop-start))
View Code

I/O密集型

技术分享图片
from multiprocessing import Process
from threading import Thread
import os,time

def task():
    time.sleep(2)

if __name__ == __main__:
    l=[]
    print(os.cpu_count()) #本机为4核
    start=time.time()
    for i in range(400):
        # p=Process(target=task) #耗时29.650749683380127s
        p=Thread(target=task) #耗时2.0773582458496094s
        l.append(p)
        p.start()
    for p in l:
        p.join()
    stop=time.time()
    print(run time is %s %(stop-start))
View Code

 

 

二、死锁现象与递归锁

死锁现象

就是线程1拿到线程2需要的那把锁,线程2拿着线程1需要的那把锁,双方拿着对方需要的资源,但是双方都无法释放

就好比我我被锁在这个房间里,我的手里拿着隔壁房间的钥匙,而另一个人被锁在隔壁的房间,他手里拿着我这个房间的钥匙,我们都需要对方的钥匙,但是都被锁住了

这就是死锁,是指两个或两个以上的进程或线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

技术分享图片
from threading import Thread,Lock
import time

mutexA=Lock()
mutexB=Lock()


class Mythead(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print(%s 抢到A锁 %self.name)
        mutexB.acquire()
        print(%s 抢到B锁 %self.name)
        mutexB.release()
        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print(%s 抢到了B锁 %self.name)
        time.sleep(2)
        mutexA.acquire()
        print(%s 抢到了A锁 %self.name)
        mutexA.release()
        mutexB.release()

if __name__ == __main__:
    for i in range(100):
        t=Mythead()
        t.start()
View Code

递归锁

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

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

技术分享图片
from threading import Thread,Lock,RLock
import time

mutexB=mutexA=RLock()


class Mythead(Thread):
    def run(self):
        self.f1()
        self.f2()

    def f1(self):
        mutexA.acquire()
        print(%s 抢到A锁 %self.name)
        mutexB.acquire()
        print(%s 抢到B锁 %self.name)
        mutexB.release()
        mutexA.release()

    def f2(self):
        mutexB.acquire()
        print(%s 抢到了B锁 %self.name)
        time.sleep(2)
        mutexA.acquire()
        print(%s 抢到了A锁 %self.name)
        mutexA.release()
        mutexB.release()

if __name__ == __main__:
    for i in range(100):
        t=Mythead()
        t.start()
View Code

 

 

三、信号量

信号量Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有5个坑,那最多只允许5个人上厕所,后面的人只能等里面有人出来了才能再进去,如果指定信号量为5,那么来一个人获得一把锁,计数加1,当计数等于5时,后面的人均需要等待。一旦释放,就有人可以获得一把锁

技术分享图片
from threading import Thread,Semaphore
import time,random
sm=Semaphore(5)

def task(name):
    sm.acquire()
    print(%s 正在上厕所 %name)
    time.sleep(random.randint(1,3))
    sm.release()

if __name__ == __main__:
    for i in range(20):
        t=Thread(target=task,args=(路人%s %i,))
        t.start()
View Code

 

四、Event事件

当一个线程需要根据另一个线程才能判断是否执行,比如红绿灯路口,车辆是否能行驶,就依靠红绿灯给出的信息才行驶。为了解决这个问题就需要Event事件

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

技术分享图片
from threading import Thread,Event
import time

event=Event()

def light():
    print(红灯正亮着)
    time.sleep(3)
    event.set() #绿灯亮

def car(name):
    print(车%s正在等绿灯 %name)
    event.wait() #等灯绿
    print(车%s通行 %name)

if __name__ == __main__:
    # 红绿灯
    t1=Thread(target=light)
    t1.start()
    #
    for i in range(10):
        t=Thread(target=car,args=(i,))
        t.start()
View Code

 

五、线程queue

 

先进先出queue.Queue() 

技术分享图片
import queue
q=queue.Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
View Code

后进先出->堆栈queue.LifoQueue() 

技术分享图片
import queue
q=queue.LifoQueue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
View Code

优先级queue.PriorityQueue()

技术分享图片
import queue
q=queue.PriorityQueue(3) #优先级,优先级用数字表示,数字越小优先级越高
q.put((10,a))
q.put((-1,b))
q.put((100,c))
print(q.get())
print(q.get())
print(q.get())
View Code

 

以上是关于Python入门学习-DAY36-GIL全局解释器锁死锁现象与递归锁信号量Event事件线程queue的主要内容,如果未能解决你的问题,请参考以下文章

Python入门学习-DAY15-模块

Day1-Python入门

学习python:day9

Python学习之路--Day9

Python-基础学习-Day1

day01语法python入门