面试题

1|0面试题

2|01 什么是gil锁

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

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

4|03 python的垃圾回收机制是什么样的?

高级一点的语言,为了保证内存的使用效率,都会有垃圾回收机制,而咱们python使用以下三种方式来做垃圾回收
1.引用计数 有多少变量指向他,他的引用计数就为几,当引用计数为0 的时候就说明没有变量指向它了,这块内存空间就会被回收掉 引用计数操作的问题:循环引用问题 2.标记清除 为了解决引用计数存在的循环引用的问题 第一阶段就标记阶段,它会把所有的“活动对象”打上标记,第二阶段把那些没有标记的“非活动对象”进行回收 # 简而言之,它会把循环引用的内存空间,打上标记,然后回收掉 3.分代回收 把对象分为三代,一开始,对象在创建的时候,放在一代中,如果在一次一代的垃圾回收检查中,该对象存活下来,就会被放到二代中,同理在一次二代的垃圾检查中,该对象存活下来,就会被放到三代中,后面优先检查第一代中的对象,优先回收,其次依次往上检查做回收

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

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

6|05 进程,线程和协程

1|0概念
进程:是资源分配的最小单位,一个应用程序应用起来,至少需要一个进程,在进程管理器(资源管理器)中可以看到一个个进程 线程:是cpu调度,执行的最小单位,一个进程下至少有一个线程 协程:单线程下的并发,程序层面控制的任务切换
1|0代码如何实现
# 开启进程的两种方法: 1.写一个类,继承Process,重写类的run方法,实例化得到对象,对象.start开启进程 2.通过Process类实例化一个对象,传入一个任务,调用对象.start开启进程 # 开启线程的两种方法: 1.写一个类,继承Thread,重写类的run方法,实例化得到对象,对象.start开启线程 2.通过Thread类实例化一个对象,传入一个任务,调用对象.start开启进程 # 开启协程 1.早期的写法:借助于第三方gevent,基于greelet写的 2.现在写法: asyncawait关键字,不借助于第三方,开启协程asyacio包 写在一个函数前,async def task()--->这个函数执行的结果就是协程函数 await只要是io操作的代码,前面必须加await
1|0三者我们在哪里用过
1.一般遇到计算密集型的操作,我会开多进程,遇到io密集型的操作,我会开多线程 2.闲着没事干的时候,爬取别的数据,喜欢开多线程,爬虫io居多 3.程序中,异步做一件事情,也可以开多线程 eg: 一个视图函数,异步的把数据写到文件中 异步的发送钉钉通知 异步的发送邮件 实际生活中,在项目中,不需要我们来开启进程线程的,可以借助于第三方的框架比如celery就可以进行异步操作 celery的worker,就是进程线程架构 4.django框架,是支持并发的,我们没有开启多进程、多线程,但是符合uwsgi的web服务器在进入Django框架之前,就开启了线程和进程来执行视图函数

7|06 什么是鸭子类型

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

image


__EOF__

本文作者泡芙有点甜
本文链接https://www.cnblogs.com/zx0524/p/17303467.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   小王应该在学习!  阅读(25)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示