您当前的位置:首页 > 计算机 > 编程开发 > Python

python爬虫面试精选02集(进程、线程面经)

时间:04-23来源:作者:点击数:

进程、线程概念

  • 进程与线程关系
    在这里插入图片描述
  • 进程
    程序在计算机中的一次执行过程。
    程序是一个可执行的文件,是静态的占有磁盘。
    进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
  • 进程的状态
    • 就绪态 : 进程具备执行条件,等待系统调度分配cpu资源
    • 运行态 : 进程占有cpu正在运行
    • 等待态 : 进程阻塞等待,此时会让出cpu
    • 五态 (在三态基础上增加新建和终止)
        新建 : 创建一个进程,获取资源的过程
      
        终止 : 进程结束,释放资源的过程
      
  • 线程
    线程被称为轻量级的进程,也是多任务编程方式
    也可以利用计算机的多cpu资源
    线程可以理解为进程中再开辟的分支任务
  • 线程特征
    1. 一个进程中可以包含多个线程
    2. 线程也是一个运行行为,消耗计算机资源
    3. 一个进程中的所有线程共享这个进程的资源
    4. 多个线程之间的运行同样互不影响各自运行
    5. 线程的创建和销毁消耗资源远小于进程
  • 进程是资源分配的最小单位,线程是CPU调度的最小单位

线程与进程的区别可以归纳为以下4点:

  • 地址空间和其它资源(如打开文件):
    进程间相互独立,同一进程的各线程间资源共享
    某进程内的线程在其它进程不可见。
  • 通信:
    进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  • 调度和切换:
    线程上下文切换比进程上下文切换要快得多
  • 在多线程OS中,进程不是一个可执行的实体。

多进程和多线程的比较

对比维度 多进程 多线程 总结
数据共享、同步 数据共享复杂,同步简单 数据共享简单,同步复杂 各有优劣
内存、CPU 占用内存,切换复杂,CPU利用率 占用内存,切换简单,CPU利用率 线程占优
创建、销毁、切换 复杂,速度慢 简单,速度快 线程占优
编程、调试 编程简单,调试简单 编程复杂,调试复杂 进程占优
可靠性 进程间不会互相影响 一个线程挂掉将导致整个进程挂掉 进程占优
分布式 适用于多核、多机,扩展到多台机器简单 适合于多核 进程占优

进程与线程总结

  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到该趟火车的所有车厢)
  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-”互斥锁(mutex)”
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量(semaphore)”

进程编程

  • 使用模块 : multiprocessing
  • 创建流程
    1. 将需要新进程执行的事件封装为函数
    2. 通过模块的Process类创建进程对象,关联函数
    3. 可以通过进程对象设置进程信息及属性
    4. 通过进程对象调用start启动进程
    5. 通过进程对象调用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

线程编程

  • 创建步骤
    1. 继承Thread类
    2. 重写__init__方法添加自己的属性,使用super()加载父类属性
    3. 重写run()方法
  • 使用方法
    1. 实例化对象
    2. 调用start自动执行run方法
    3. 调用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)

僵尸与孤儿

  • 孤儿进程 : 父进程先于子进程退出,此时子进程成为孤儿进程。【失去双亲,父先退出】
    • 特点:孤儿进程会被系统进程收养此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。
  • 僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程。
    • 特点: 僵尸进程虽然结束,但是会存留部分进程信息资源在内存中,大量的僵尸进程会浪费系统的内存资源。
    • 如何避免僵尸进程产生
      1. 使用join()回收
      2. 在父进程中使用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多线程只适用于执行有阻塞延迟的任务情形
方便获取更多学习、工作、生活信息请关注本站微信公众号城东书院 微信服务号城东书院 微信订阅号
推荐内容
相关内容
栏目更新
栏目热门