当前位置: 首页 > news >正文

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

 

转载于:https://www.cnblogs.com/xvchengqi/p/9605470.html

相关文章:

  • SqlMap使用
  • Maven打war包命令
  • Linux常用Office办公软件
  • 如何在Eclipse下查看JDK源代码
  • legend---三、方法集思路
  • [POI2007] ZAP-Queries (莫比乌斯反演)
  • re:从零开始的数位dp
  • I/O多路复用
  • Nginx配置HTTPS
  • 正则表达式 整理
  • 分布式版本控制系统Git的安装与使用
  • 【BZOJ 4551】【TJOI2016】【HEOI2016】树
  • oracle多表查询-自连接
  • swiper 点击切换,拖动切换后继续自动轮播
  • Python 之 文件操作
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • Apache Spark Streaming 使用实例
  • conda常用的命令
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • Laravel 菜鸟晋级之路
  • Theano - 导数
  • Vue.js源码(2):初探List Rendering
  • 程序员该如何有效的找工作?
  • 给第三方使用接口的 URL 签名实现
  • 欢迎参加第二届中国游戏开发者大会
  • 记一次删除Git记录中的大文件的过程
  • 罗辑思维在全链路压测方面的实践和工作笔记
  • 如何使用Mybatis第三方插件--PageHelper实现分页操作
  • Nginx实现动静分离
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • #define
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (附源码)springboot猪场管理系统 毕业设计 160901
  • (附源码)计算机毕业设计SSM智慧停车系统
  • (四)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)
  • ***汇编语言 实验16 编写包含多个功能子程序的中断例程
  • .“空心村”成因分析及解决对策122344
  • .NET DevOps 接入指南 | 1. GitLab 安装
  • .Net Winform开发笔记(一)
  • .NET 设计模式—简单工厂(Simple Factory Pattern)
  • .net6使用Sejil可视化日志
  • .Net小白的大学四年,内含面经
  • .Net中的设计模式——Factory Method模式
  • .sh 的运行
  • /etc/sudoer文件配置简析
  • @德人合科技——天锐绿盾 | 图纸加密软件有哪些功能呢?
  • [].slice.call()将类数组转化为真正的数组
  • [2008][note]腔内级联拉曼发射的,二极管泵浦多频调Q laser——
  • [28期] lamp兄弟连28期学员手册,请大家务必看一下
  • [ACL2022] Text Smoothing: 一种在文本分类任务上的数据增强方法
  • [AIGC] MySQL存储引擎详解
  • [Big Data - Kafka] kafka学习笔记:知识点整理
  • [C++]四种方式求解最大子序列求和问题
  • [HTML]Web前端开发技术28(HTML5、CSS3、JavaScript )JavaScript基础——喵喵画网页
  • [NowCoder]牛客OI周赛3