一文讲透 “进程、线程、协程”

发表于 4年以前  | 总阅读数:482 次

本文从操作系统原理出发结合代码实践讲解了以下内容:

  • 什么是进程,线程和协程?
  • 它们之间的关系是什么?
  • 为什么说Python中的多线程是伪多线程?
  • 不同的应用场景该如何选择技术方案?
  • ...

什么是进程

进程-操作系统提供的抽象概念,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。程序本身是没有生命周期的,它只是存在磁盘上的一些指令,程序一旦运行就是进程。

当程序需要运行时,操作系统将代码和所有静态数据记载到内存和进程的地址空间(每个进程都拥有唯一的地址空间,见下图所示)中,通过创建和初始化栈(局部变量,函数参数和返回地址)、分配堆内存以及与 IO 相关的任务,当前期准备工作完成,启动程序,OS将CPU的控制权转移到新创建的进程,进程开始运行。

操作系统对进程的控制和管理通过 PCB(Processing Control Block),PCB 通常是系统内存占用区中的一个连续存区,它存放着操作系统用于描述进程情况及控制进程运行所需的全部信息(进程标识号,进程状态,进程优先级,文件系统指针以及各个寄存器的内容等),进程的 PCB 是系统感知进程的唯一实体。

一个进程至少具有 5 种基本状态:初始态、执行状态、等待(阻塞)状态、就绪状态、终止状态

  • 初始状态:进程刚被创建,由于其他进程正占有 CPU 所以得不到执行,只能处于初始状态。
  • 执行状态:任意时刻处于执行状态的进程只能有一个。
  • 就绪状态:只有处于就绪状态的经过调度才能到执行状态
  • 等待状态:进程等待某件事件完成
  • 停止状态:进程结束

进程间的切换

无论是在多核还是单核系统中,一个 CPU 看上去都像是在并发的执行多个进程,这是通过处理器在进程间切换来实现的。

操作系统对把 CPU 控制权在不同进程之间交换执行的机制成为上下文切换(context switch),即保存当前进程的上下文,恢复新进程的上下文,然后将 CPU 控制权转移到新进程,新进程就会从上次停止的地方开始。因此,进程是轮流使用 CPU 的,CPU 被若干进程共享,使用某种调度算法来决定何时停止一个进程,并转而为另一个进程提供服务。

  • 单核 CPU 双进程的情况

进程直接特定的机制和遇到 I/O 中断的情况下,进行上下文切换,轮流使用 CPU 资源

  • 双核 CPU 双进程的情况

每一个进程独占一个 CPU 核心资源,在处理 I/O 请求的时候,CPU 处于阻塞状态

进程间数据共享

系统中的进程与其他进程共享 CPU 和主存资源,为了更好的管理主存,现在系统提供了一种对主存的抽象概念,即为虚拟存储器(VM)。它是一个抽象的概念,它为每一个进程提供了一个假象,即每个进程都在独占地使用主存。

虚拟存储器主要提供了三个能力:

  • 将主存看成是一个存储在磁盘上的高速缓存,在主存中只保存活动区域,并根据需要在磁盘和主存之间来回传送数据,通过这种方式,更高效地使用主存
  • 为每个进程提供了一致的地址空间,从而简化了存储器管理
  • 保护了每个进程的地址空间不被其他进程破坏

由于进程拥有自己独占的虚拟地址空间,CPU 通过地址翻译将虚拟地址转换成真实的物理地址,每个进程只能访问自己的地址空间。因此,在没有其他机制(进程间通信)的辅助下,进程之间是无法共享数据的

  • 以 python 中 multiprocessing 为例
import multiprocessing
import threading
import time

n = 0


def count(num):
    global n
    for i in range(100000):
        n += i
    print("Process {0}:n={1},id(n)={2}".format(num, n, id(n)))


if __name__ == '__main__':
    start_time = time.time()

    process = list()
    for i in range(5):
        p = multiprocessing.Process(target=count, args=(i,)) # 测试多进程使用
        # p = threading.Thread(target=count, args=(i,))  # 测试多线程使用
        process.append(p)

    for p in process:
        p.start()

    for p in process:
        p.join()

    print("Main:n={0},id(n)={1}".format(n, id(n)))
    end_time = time.time()
    print("Total time:{0}".format(end_time - start_time))
  • 结果
Process 1:n=4999950000,id(n)=139854202072440
Process 0:n=4999950000,id(n)=139854329146064
Process 2:n=4999950000,id(n)=139854202072400
Process 4:n=4999950000,id(n)=139854201618960
Process 3:n=4999950000,id(n)=139854202069320
Main:n=0,id(n)=9462720
Total time:0.03138256072998047

变量 n 在进程 p{0,1,2,3,4} 和主进程(main)中均拥有唯一的地址空间

什么是线程

线程-也是操作系统提供的抽象概念,是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,同一进程中的多个线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈和线程本地存储(如下图所示)。

系统利用 PCB 来完成对进程的控制和管理。同样,系统为线程分配一个线程控制块TCB(Thread Control Block),将所有用于控制和管理线程的信息记录在线程的控制块中, TCB 中通常包括:

  • 线程标志符
  • 一组寄存器
  • 线程运行状态
  • 优先级
  • 线程专有存储区
  • 信号屏蔽

和进程一样,线程同样有五种状态:初始态、执行状态、等待(阻塞)状态、就绪状态和终止状态,线程之间的切换和进程一样也需要上下文切换,这里不再赘述。

进程和线程之间有许多相似的地方,那它们之间到底有什么区别呢?

进程 VS 线程

  • 进程是资源的分配和调度的独立单元。进程拥有完整的虚拟地址空间,当发生进程切换时,不同的进程拥有不同的虚拟地址空间。而同一进程的多个线程是可以共享同一地址空间
  • 线程是 CPU 调度的基本单元,一个进程包含若干线程。
  • 线程比进程小,基本上不拥有系统资源。线程的创建和销毁所需要的时间比进程小很多
  • 由于线程之间能够共享地址空间,因此,需要考虑同步和互斥操作
  • 一个线程的意外终止会影响整个进程的正常运行,但是一个进程的意外终止不会影响其他的进程的运行。因此,多进程程序安全性更高。

总之,多进程程序安全性高,进程切换开销大,效率低;多线程程序维护成本高,线程切换开销小,效率高。(python 的多线程是伪多线程,下文中将详细介绍

什么是协程

协程(Coroutine,又称微线程)是一种比线程更加轻量级的存在,协程不是被操作系统内核所管理,而完全是由程序所控制。协程与线程以及进程的关系见下图所示。

  • 协程可以比作子程序,但执行过程中,子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。协程之间的切换不需要涉及任何系统调用或任何阻塞调用
  • 协程只在一个线程中执行,是子程序之间的切换,发生在用户态上。而且,线程的阻塞状态是由操作系统内核来完成,发生在内核态上,因此协程相比线程节省线程创建和切换的开销
  • 协程中不存在同时写变量冲突,因此,也就不需要用来守卫关键区块的同步性原语,比如互斥锁、信号量等,并且不需要来自操作系统的支持。

协程适用于 IO 阻塞且需要大量并发的场景,当发生 IO 阻塞,由协程的调度器进行调度,通过将数据流 yield 掉,并且记录当前栈上的数据,阻塞完后立刻再通过线程恢复栈,并把阻塞的结果放到这个线程上去运行。

下面,将针对在不同的应用场景中如何选择使用 Python 中的进程,线程,协程进行分析。

如何选择?

在针对不同的场景对比三者的区别之前,首先需要介绍一下 python 的多线程(一直被程序员所诟病,认为是"假的"多线程)。

那为什么认为 Python 中的多线程是“伪”多线程呢?

更换上面 multiprocessing 示例中, p=multiprocessing.Process(target=count,args=(i,))为 p=threading.Thread(target=count,args=(i,)),其他照旧,运行结果如下:

为了减少代码冗余和文章篇幅,命名和打印不规则问题请忽略

Process 0:n=5756690257,id(n)=140103573185600
Process 2:n=10819616173,id(n)=140103573185600
Process 1:n=11829507727,id(n)=140103573185600
Process 4:n=17812587459,id(n)=140103573072912
Process 3:n=14424763612,id(n)=140103573185600
Main:n=17812587459,id(n)=140103573072912
Total time:0.1056210994720459
  • n 是全局变量,Main 的打印结果与线程相等,证明了线程之间是数据共享

但是,为什么多线程运行时间比多进程还要长?这与我们上面所说(线程的开销<<进程的开销)的严重不相符啊。这就是轮到 Cpython(python 默认的解释器)中 GIL(Global Interpreter Lock,全局解释锁)登场了。

什么是GIL

GIL来源于Python设计之初的考虑,为了数据安全(由于内存管理机制中采用引用计数)所做的决定。某个线程想要执行,必须先拿到 GIL。因此,可以把 GIL 看作是“通行证”,并且在一个 Python进程中,GIL 只有一个,拿不到通行证的线程,就不允许进入 CPU 执行。

Cpython 解释器在内存管理中采用引用计数,当对象的引用次数为 0 时,会将对象当作垃圾进行回收。设想这样一种场景:

一个进程中含有两个线程,分别为线程 0 和线程 1,两个线程全都引用对象 a。当两个线程同时对 a 发生引用(并未修改,不需要使用同步性原语),就会发生同时修改对象 a 的引用计数器,造成计数器引用少于实质性的引用,当进行垃圾回收时,造成错误异常。因此,需要一把全局锁(即为GIL)来保证对象引用计数的正确性和安全性。

无论是单核还是多核,一个进程永远只能同时执行一个线程(拿到 GIL 的线程才能执行,如下图所示),这就是为什么在多核 CPU 上,Python 的多线程效率并不高的根本原因。

那是不是在 Python 中遇到并发的需求就使用多进程就万事大吉了呢?其实不然,软件工程中有一句名言:没有银弹!

何时用?

常见的应用场景不外乎三种:

  • CPU 密集型:程序需要占用 CPU 进行大量的运算和数据处理;
  • I/O 密集型:程序中需要频繁的进行 I/O 操作;例如网络中 socket 数据传输和读取等;
  • CPU 密集+I/O 密集:以上两种的结合

CPU 密集型的情况可以对比以上 multiprocessing 和 threading 的例子,多进程的性能 > 多线程的性能。

下面主要解释一下 I/O 密集型的情况。与 I/O 设备交互,目前最常用的解决方案就是 DMA

什么是 DMA

DMA(Direct Memory Access) 是系统中的一个特殊设备,它可以协调完成内存到设备间的数据传输,中间过程不需要 CPU 介入。

以文件写入为例:

  • 进程 p1 发出数据写入磁盘文件的请求
  • CPU 处理写入请求,通过编程告诉 DMA 引擎数据在内存的位置,要写入数据的大小以及目标设备等信息
  • CPU 处理其他进程 p2 的请求,DMA 负责将内存数据写入到设备中
  • DMA 完成数据传输,中断 CPU
  • CPU 从 p2 上下文切换到 p1,继续执行 p1

Python多线程的表现(I/O密集型)

  • 线程Thread0首先执行,线程Thread1等待(GIL的存在)
  • Thread0收到I/O请求,将请求转发给DMA,DMA执行请求
  • Thread1占用CPU资源,继续执行
  • CPU收到DMA的中断请求,切换到Thread0继续执行

与进程的执行模式相似,弥补了 GIL 带来的不足,又由于线程的开销远远小于进程的开销,因此,在 IO 密集型场景中,多线程的性能更高

实践是检验真理的唯一标准,下面将针对 I/O 密集型场景进行测试。

测试

  • 执行代码
import multiprocessing
import threading
import time


def count(num):
    time.sleep(1)  ## 模拟IO操作
    print("Process {0} End".format(num))


if __name__ == '__main__':
    start_time = time.time()
    process = list()
    for i in range(5):
        p = multiprocessing.Process(target=count, args=(i,))
        # p = threading.Thread(target=count, args=(i,))
        process.append(p)

    for p in process:
        p.start()

    for p in process:
        p.join()

    end_time = time.time()
    print("Total time:{0}".format(end_time - start_time))
  • 结果
## 多进程
Process 0 End
Process 3 End
Process 4 End
Process 2 End
Process 1 End
Total time:1.383193016052246
## 多线程
Process 0 End
Process 4 End
Process 3 End
Process 1 End
Process 2 End
Total time:1.003425121307373
  • 多线程的执行效性能高于多进程

是不是认为这就结束了?远还没有呢。针对 I/O 密集型的程序,协程的执行效率更高,因为它是程序自身所控制的,这样将节省线程创建和切换所带来的开销。

以 Python 中 asyncio 应用为依赖,使用 async/await 语法进行协程的创建和使用。

  • 程序代码
import time
import asyncio


async def coroutine():
    await asyncio.sleep(1) ## 模拟IO操作


if __name__ == "__main__":
    start_time = time.time()

    loop = asyncio.get_event_loop()
    tasks = []
    for i in range(5):
        task = loop.create_task(coroutine())
        tasks.append(task)

    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    end_time = time.time()
    print("total time:", end_time - start_time)
  • 结果

total time: 1.001854419708252
  • 协程的执行效性能高于多线程

总结

本文从操作系统原理出发结合代码实践讲解了进程,线程和协程以及他们之间的关系。并且,总结和整理了 Python 实践中针对不同的场景如何选择对应的方案,如下:

  • CPU 密集型:多进程
  • IO 密集型:多线程(协程维护成本较高,而且在读写文件方面效率没有显著提升)
  • CPU 密集和 IO 密集:多进程+协程

本文由哈喽比特于4年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/-a9AdVjNrDLoLeFosuSz1Q

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:1年以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:1年以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:1年以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:1年以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:1年以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:1年以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:1年以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:1年以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:1年以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:1年以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:1年以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:1年以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:1年以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:1年以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:1年以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:1年以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:1年以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:1年以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:1年以前  |  398次阅读  |  详细内容 »
 相关文章
Android插件化方案 5年以前  |  237231次阅读
vscode超好用的代码书签插件Bookmarks 2年以前  |  8065次阅读
 目录