为什么有了gil锁还要互斥锁、 进程,线程和协程 、什么是鸭子类型

发布时间 2023-08-02 21:46:39作者: 岳宗柯

1 为什么有了gil锁还要互斥锁

gil:全局解释器锁,线程要执行,必须先获得到gil锁,才能执行
互斥锁:为了保证多线程并发操作数据(变量)而设置的锁,保证在加锁和释放锁之间,其他线程不能操作
gil本质也是大的互斥锁

# 出现了数据错乱,出现了多条线程操作变量,出现的并发安全问题
a=0
线程1要计算: a+=1  
	1 线程1 拿到gil  
    2 读取a=0
    3 假设时间片到了,释放gil,释放cpu
  	4 等待下次被调度执行
    10 轮到它了,获取gil锁
    11 继续往下执行:计算a+1
    12 把结果赋值给a ,a=1
	13 释放gil锁
 线程2要计算: a+=1 
    5 线程2获得了gil锁
    6 读取a=0
    7 计算a+1
    8 把结果赋值给a ,a=1
    9 释放gil锁
# 什么临界区?处出现并发安全问题的这段代码称之为临界区,临界区会出现并发安全问题,所以要在临界区加锁
	# 加锁
	6 读取a=0
    7 计算a+1
    8 把结果赋值给a ,a=1
    # 释放锁

互斥锁保证数据安全

a=0
线程1要计算: a+=1  
	1 线程1 拿到gil  
    # 加锁
    2 读取a=0
    3 假设时间片到了,释放gil,释放cpu
  	4 等待下次被调度执行
    
    7 轮到它了,获取gil锁
    8 继续往下执行:计算a+1
    9 把结果赋值给a ,a=1
	10 释放gil锁
 线程2要计算: a+=1 
    5 线程2获得了gil锁
    #获取锁,获取不到
    6 释放gil锁
    11 获得gil锁
    
    #加锁
    12 读取a=0
    13 计算a+1
    14 把结果赋值给a ,a=1
    15 释放锁
    16 释放gil锁

# gil锁并不锁住临界区,临界区需要我们自己用互斥锁加锁

2 进程,线程和协程

概念
# 进程:是资源分配的最小单位,一个应用程序运行起来,至少有一个进程,进程管理器中就可以看到一个个的进程
# 线程:是cpu调度,执行的最小单位,一个进程下至少有一个线程
# 协程:单线程下的并发,程序层面控制的任务切换

代码如何实现
# 开启多进程两种方式
	-1 写一个类,继承Process,重写类的run方法---》实例化得到对象,对象.start 开启了进程
    -2 通过Process类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
你在哪里用过

# 同理开启线程两种方式
	-1 写一个类,继承Thread,重写类的run方法---》实例化得到对象,对象.start 开启了进程
    -2 通过Thread类实例化得到一个对象,传入任务 ,调用对象.start 开启了进程
你在哪里用过


# 开启协程
	- 早期之前:借助于第三方gevent,基于greelet写的
    - async 和 await 关键字,不借助于第三方,开启协程  asyncio 包
    	-必须写在一个函数前, async def task()---->这个函数执行的结果是协程函数
        -await 只要是io操作的代码,前面必须加 await

在哪用过

-我一般遇到计算密集型的操作,我会开多进程,io密集型的操作,我一般开多线程
-闲来无事,爬别人数据,喜欢开多线程,爬虫io居多
-程序中,异步做一件事情,也可以开多线程
	比如一个视图函数,异步的吧数据写的文件中
    异步的发送钉钉通知
    异步的发送邮件
-但实际上,在项目中,不需要我们开启进程线程,可以借助于第三方的框架比如celery就可以做异步的操作
而celery的worker,就是进程线程架构
-django框架,是支持并发,我们没有开启多进程和多线程,但是符合uwsgi的web服务器在进入djagno框架之前,开启了进程和线程来执行视图函数

3 什么是鸭子类型

-走路像鸭子,说话像鸭子,我们就可以叫它叫鸭子
-解释:鸭子类型是python面向对象中描述接口的一个概念,区分与其他编程语言,
	比如java:实现接口,必须显示的继承一个接口
    而python:实现接口,遵循鸭子类型,不需要显示的继承一个接口(类),只要类中有对应的属性跟方法,我们就称这几个类的对象为同一种类型