Python进程、线程面试经验总结
毕业季当下的你,还在为米那是烦恼吗?下面给大家带来Python进程线程的面试总结
进程、线程概念
-
进程与线程关系
-
进程
程序在计算机中的一次执行过程。
程序是一个可执行的文件,是静态的占有磁盘。
进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
-
进程的状态
-
就绪态 : 进程具备执行条件,等待系统调度分配cpu资源
扫描二维码关注公众号,回复: 12860916 查看本文章 -
运行态 : 进程占有cpu正在运行
-
等待态 : 进程阻塞等待,此时会让出cpu
-
五态 (在三态基础上增加新建和终止)
新建 : 创建一个进程,获取资源的过程 终止 : 进程结束,释放资源的过程
-
-
线程
线程被称为轻量级的进程,也是多任务编程方式
也可以利用计算机的多cpu资源
线程可以理解为进程中再开辟的分支任务
-
线程特征
-
一个进程中可以包含多个线程
-
线程也是一个运行行为,消耗计算机资源
-
一个进程中的所有线程共享这个进程的资源
-
多个线程之间的运行同样互不影响各自运行
-
线程的创建和销毁消耗资源远小于进程
-
-
进程是资源分配的最小单位,线程是CPU调度的最小单位
线程与进程的区别可以归纳为以下4点:
-
地址空间和其它资源(如打开文件):
进程间
相互独立
,同一进程的各线程间资源共享
。某进程内的线程在其它进程不可见。
-
通信:
进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
-
调度和切换:
线程
上下文切换比进程
上下文切换要快得多
。 -
在多线程OS中,进程不是一个可执行的实体。
多进程和多线程的比较
对比维度 | 多进程 | 多线程 | 总结 |
---|---|---|---|
数据共享、同步 | 数据共享复杂,同步简单 | 数据共享简单,同步复杂 | 各有优劣 |
内存、CPU | 占用内存多 ,切换复杂,CPU利用率低 |
占用内存少 ,切换简单,CPU利用率高 |
线程占优 |
创建、销毁、切换 | 复杂,速度慢 |
简单,速度快 |
线程占优 |
编程、调试 | 编程简单,调试简单 |
编程复杂,调试复杂 |
进程占优 |
可靠性 | 进程间不会互相影响 | 一个线程挂掉将导致整个进程挂掉 | 进程占优 |
分布式 | 适用于多核、多机,扩展到多台机器简单 | 适合于多核 | 进程占优 |
进程与线程总结
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到该趟火车的所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-”互斥锁(mutex)”
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量(semaphore)”
进程编程
-
使用模块 : multiprocessing
-
创建流程
-
将需要新进程执行的事件封装为函数
-
通过模块的Process类创建进程对象,关联函数
-
可以通过进程对象设置进程信息及属性
-
通过进程对象调用start启动进程
-
通过进程对象调用join回收进程资源
-
"""
multiprocessing
1.将需要新进程执行的事件封装为函数
2 .通过模块的Process类创建进程对象,关联函数
3 .通过进程对象调用start启动进程
4 .通过进程对象调用join回收进程资源
"""
import multiprocessing as mp
from time import sleep
a = 1
# 进程函数
def fun():
global a
print("开始一个进程")
sleep(2)
a = 1000
print("a = ",a)
print("进程结束了,实际也没干啥")
if __name__ == '__main__':
# 创建进程对象
p = mp.Process(target=fun) # 绑定函数 此时还没有创建进程
# start启动进程 自动执行fun函数,作为一个进程执行
p.start() # 此时进程才产生
print("原有进程也干点事")
sleep(3)
print("原有进程其实也没干啥")
# 回收进程
p.join()
print("a :",a) # a = 1
线程编程
-
创建步骤
-
继承Thread类
-
重写
__init__
方法添加自己的属性,使用super()加载父类属性 -
重写run()方法
-
-
使用方法
-
实例化对象
-
调用start自动执行run方法
-
调用join回收线程
-
"""
threading 创建线程演示
"""
from threading import Thread
from time import sleep
import os
a = 1 # 全局变量
# 线程函数
def music():
for i in range(3):
sleep(2)
print(os.getpid(),"播放:黄河大合唱")
global a
print("a =",a)
a = 1000
# 创建线程对象
t = Thread(target=music)
t.start() # 启动线程 执行music
for i in range(4):
sleep(1)
print(os.getpid(),"播放:葫芦娃")
t.join() # 回收线程资源
print("a:",a)"""
threading 创建线程演示
"""
from threading import Thread
from time import sleep
import os
a = 1 # 全局变量
# 线程函数
def music():
for i in range(3):
sleep(2)
print(os.getpid(),"播放:黄河大合唱")
global a
print("a =",a)
a = 1000
# 创建线程对象
t = Thread(target=music)
t.start() # 启动线程 执行music
for i in range(4):
sleep(1)
print(os.getpid(),"播放:葫芦娃")
t.join() # 回收线程资源
print("a:",a)
僵尸与孤儿
-
孤儿进程 : 父进程先于子进程退出,此时子进程成为孤儿进程。【失去双亲,父先退出】
- 特点: 孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。
-
僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程。
-
特点: 僵尸进程虽然结束,但是会存留部分进程信息资源在内存中,大量的僵尸进程会浪费系统的内存资源。
-
如何避免僵尸进程产生
- 使用join()回收
- 在父进程中使用signal方法处理【一劳永逸,window下用不了】
-
"""
孤儿进程和僵尸进程演示
"""
from multiprocessing import Process
from time import sleep
import os
from signal import *
def fun():
print("这是一个子进程",os.getppid(),'---',os.getpid())
sleep(3)
print("注定成为孤儿进程", os.getppid(), '---', os.getpid())
if __name__ == '__main__':
signal(SIGCHLD,SIG_IGN) # 系统方法处理僵尸进程,所有子进程退出由系统处理
p = Process(target=fun)
p.start()
p.join() # 防止僵尸产生
# 大量工作进入死循环
while True:
pass
死锁
- 死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁。【逻辑混乱造成的,多个锁互相阻塞,造成程序无法运行】
-
死锁产生条件
-
互斥条件:指线程使用了互斥方法,使用一个资源时其他线程无法使用。【三方互相欠钱问题,考逻辑解决】
-
请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求,在获取到新的资源前不会释放自己保持的资源。
-
不剥夺条件:不会受到线程外部的干扰,如系统强制终止线程等。
-
环路等待条件:指在发生死锁时,必然存在一个线程——资源的环形链,如 T0正在等待一个T1占用的资源;T1正在等待T2占用的资源,……,Tn正在等待已被T0占用的资源。
-
-
如何避免死锁
- 逻辑清晰,不要同时出现上述死锁产生的四个条件
- 通过测试工程师进行死锁检测
GIL 全局解释器锁
-
什么是GIL问题 (全局解释器锁)【python线程是个鸡肋,不行8?】
由于python解释器设计中加入了解释器锁,导致python解释器同一时刻只能解释执行一个线程,大大降低了线程的执行效率。
-
导致后果
因为遇到阻塞时线程会主动让出解释器,去解释其他线程。所以python多线程在执行多阻塞任务时可以提升程序效率,其他情况并不能对效率有所提升。【多线程通过sleep阻塞提高多线程效率】
【直接后果:同一时刻只能解释一个线程】
【效率:在处理无阻塞的任务时效率低】
【结论:Python的线程只适合处理高效处理高阻塞延迟的任务】
-
GIL问题建议
* 尽量使用进程完成无阻塞的并发行为
* 不使用c作为解释器 (Java C#)
Guido的声明:<http://www.artima.com/forums/flat.jsp?forum=106&thread=214235>
- 结论
- GIL问题与Python语言本身并没什么关系,属于解释器设计的历史问题。
- 在无阻塞状态下,多线程程序程序执行效率并不高,甚至还不如单线程效率。
- Python多线程只适用于执行有阻塞延迟的任务情形。