面试题

面试题

1 什么是gil锁

gil锁:全局解释器锁,他的本质是一个大的互斥锁,他是cpython的一种机制,gil只存在cpython解释器,他限制了一个线程只有获取到gil锁才能执行,如果没有拿到gil锁,线程是不能执行的

解释器有:cpython,pypython,jpython
         
gil锁的作用是什么?
	限制线程只有获取到gil锁才能执行
为什么要有gil锁?保证数据安全?
	保证数据安全用互斥锁就好了,gil锁不能保证数据安全
    Python的垃圾回收机制,需要用逻辑回收线程来做,如果在同一时刻有多个线程在同时执行,垃圾回收机制会把正在其他线程使用的变量给回收掉,因为当时只有单核电脑,本来同一时刻就不能有多个线程同时执行,于是作者就干脆做了一个gil锁,让线程必须获取到gil锁,才能执行,但后来随着多核的出现,导致python的多线程并不能利用多核

2 为什么有了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锁并不锁住临界区,临界区需要我们自己用互斥锁加锁

3 python的垃圾回收机制是什么样的?

高级一点的语言,为了保证内存的使用效率,都会有垃圾回收机制,而咱们python使用以下三种方式来做垃圾回收
1.引用计数
	有多少变量指向他,他的引用计数就为几,当引用计数为0 的时候就说明没有变量指向它了,这块内存空间就会被回收掉
    引用计数操作的问题:循环引用问题

2.标记清除
	为了解决引用计数存在的循环引用的问题
    第一阶段就标记阶段,它会把所有的“活动对象”打上标记,第二阶段把那些没有标记的“非活动对象”进行回收
    # 简而言之,它会把循环引用的内存空间,打上标记,然后回收掉

3.分代回收
	把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾回收检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中,后面优先检查第一代中的对象,优先回收,其次依次往上检查做回收

4 解释为什么计算密集型用多进程,io密集型用多线程

由于GIL锁的存在,即便是多核机器,同一时刻,也只能有一个线程在执行
    -线程需要cpu去调度执行
    -如果开了多线程,是计算密集型,计算是消耗cpu,假设是四核电脑,不能充分利用这四个核,只能有一个核在执行,开多线程没有用
    -而如果计算密集型,开了多进程,gil是在cpython解释器进程中的,再进程中开启线程执行计算,可以充分利用多核优势
    -开了一个进程,就开了一个cpython解释器的进程,就会有一个gil锁
    
    -由于GIL锁的存在,如果是计算密集型,开启多线程,不能利用多核优势,
    -开启多进程,可以利用多核优势
    -io不耗费cpu,开启多线程,在一个时间段内是有并发效果的
     
    -即便是io密集型,用多进程是不是会显著提高效率?
    	本身开启进程是非常消耗资源的,如果是io密集型,没有必要开多进程,并不会有显著的效率提升

5 进程,线程和协程

概念
进程:是资源分配的最小单位,一个应用程序应用起来,至少需要一个进程,在进程管理器(资源管理器)中可以看到一个个进程

线程:是cpu调度,执行的最小单位,一个进程下至少有一个线程

协程:单线程下的并发,程序层面控制的任务切换
代码如何实现
# 开启进程的两种方法:
1.写一个类,继承Process,重写类的run方法,实例化得到对象,对象.start开启进程
2.通过Process类实例化一个对象,传入一个任务,调用对象.start开启进程

# 开启线程的两种方法:
1.写一个类,继承Thread,重写类的run方法,实例化得到对象,对象.start开启线程
2.通过Thread类实例化一个对象,传入一个任务,调用对象.start开启进程

# 开启协程
1.早期的写法:借助于第三方gevent,基于greelet写的
2.现在写法:
	有async和await关键字,不借助于第三方,开启协程asyacio包
    写在一个函数前,async def task()--->这个函数执行的结果就是协程函数
    await只要是io操作的代码,前面必须加await
三者我们在哪里用过
1.一般遇到计算密集型的操作,我会开多进程,遇到io密集型的操作,我会开多线程
2.闲着没事干的时候,爬取别的数据,喜欢开多线程,爬虫io居多
3.程序中,异步做一件事情,也可以开多线程
	eg:
        一个视图函数,异步的把数据写到文件中
        异步的发送钉钉通知
        异步的发送邮件
	实际生活中,在项目中,不需要我们来开启进程线程的,可以借助于第三方的框架比如celery就可以进行异步操作
    celery的worker,就是进程线程架构
4.django框架,是支持并发的,我们没有开启多进程、多线程,但是符合uwsgi的web服务器在进入Django框架之前,就开启了线程和进程来执行视图函数

6 什么是鸭子类型

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

image

posted @ 2023-04-10 16:50  小王应该在学习!  阅读(25)  评论(0编辑  收藏  举报