本文最后更新于 2024-04-18,文章内容可能已经过时。

一、进程(最小的资源单位)

进程的概念:

进程(Process):是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。

(通俗点说就是exe可执行文件运行后,应用在电脑中的状态,可以成为进程)

举例说明进程:

想象一位有一手好厨艺的计算机科学家正在为他的女儿烘制生日蛋糕。他有做生日蛋糕的食谱,厨房里有所需的原料:面粉、鸡蛋、糖、香草汁等。在这个比喻中,做蛋糕的食谱就是程序(即用适当形式描述的算法)计算机科学家就是处理器(CPU),而做蛋糕的各种原料就是输入数据。进程就是厨师阅读食谱、取来各种原料以及烘制蛋糕等一系列动作的总和。现在假设计算机科学家的儿子哭着跑了进来,说他的头被一只蜜蜂蛰了。计算机科学家就记录下他照着食谱做到哪儿了(保存进程的当前状态),然后拿出一本急救手册,按照其中的指示处理蛰伤。这里,我们看到处理机制是从一个进程(做蛋糕)切换到另一个高优先级的进程(实施医疗救治),每个进程拥有各自的程序(食谱和急救手册)。当蜜蜂蛰伤处理完之后,这位计算机科学家又回来做蛋糕,从他离开时的那一步继续做下去。

二、线程(最小的执行单位)

线程的概念:

线程是进程的一个实体,它被包含在进程中,一个进程至少包含一个线程,一个进程也可以包含多个线程,线程是CPU调度和分派的基本单位

线程是进程的基本执行单元,一个进程的所有任务都在线程中执行;进程要想执行任务,必须得有线程。

进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
(4)处理机分给线程,即真正在处理机上运行的是线程。
(5)线程是指进程内的一个执行单元,也是进程内的可调度实体。
线程与进程的区别:
(1)调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
(2)并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
(3)拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。
(4)系统开销:在创建或撤销进程的时候,由于系统都要为之分配和回收资源,导致系统的明显大于创建或撤销线程时的开销。但进程有独立的地址空间,进程崩溃后,在保护模式下不会对其他的进程产生影响,而线程只是一个进程中的不同的执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但是在进程切换时,耗费的资源较大,效率要差些。

举例说明线程:

假设,一个文本程序,需要接受键盘输入,将内容显示在屏幕上,还需要保存信息到硬盘中。若只有一个进程,势必造成同一时间只能干一样事的尴尬(当保存时,就不能通过键盘输入内容)。若有多个进程,每个进程负责一个任务,进程A负责接收键盘输入的任务,进程B负责将内容显示在屏幕上的任务,进程C负责保存内容到硬盘中的任务。这里进程A,B,C间的协作涉及到了进程通信问题,而且有共同都需要拥有的东西——-文本内容,不停的切换造成性能上的损失。若有一种机制,可以使任务A,B,C共享资源,这样上下文切换所需要保存和恢复的内容就少了,同时又可以减少通信所带来的性能损耗,那就好了。这种机制就是线程。

二.并行和并发 

并行处理(Parallel Processing):是计算机系统中能同时执行两个或更多个处理的一种计算方法。

并发处理(concurrency Processing):指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个CPU上运行的计算方法。

并行:同时进行多个任务
并发:短时间内,运行多个任务

无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真实干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务

三.同步和异步

在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去

异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。

四.多进程和多线程

多进程:

进程是程序在计算机上的一次执行活动。当你运行一个程序,你就启动了一个进程。进程可以分为用户进程和系统进程。

在同一个时间里,同一个计算机系统中如果允许两个或两个以上的进程处于运行状态,这便是多任务。现代的操作系统几乎都是多任务操作系统,能够同时管理多个进程的运行。

原则上一个CPU只能分配给一个进程,以便运行这个进程。我们通常使用的计算机中只有一个CPU,也就是说只有一颗心,要让它一心多用,同时运行多个进程,就必须使用并发技术。实现并发技术相当复杂,最容易理解的是“时间片轮转进程调度算法”,它的思想简单介绍如下:在操作系统的管理下,所有正在运行的进程轮流使用CPU,每个进程允许占用CPU的时间非常短(比如10毫秒),这样用户根本感觉不出来 CPU是在轮流为多个进程服务,就好像所有的进程都在不间断地运行一样。但实际上在任何一个时间内有且仅有一个进程占有CPU。

多进程的优缺点

多进程的优点

1)每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;

2)通过增加CPU,就可以容易扩充性能;

3)可以尽量减少线程加锁/解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;

4)每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大。

多进程的缺点

1)逻辑控制复杂,需要和主程序交互;

2)需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算 多进程调度开销比较大。

多线程:

在单一程序中同时运行多个想成完成不同的工作,称为多线程。

多线程是为了使得多个线程并行的工作以完成多项任务,以提高系统的效率。线程是在同一时间需要完成多项任务的时候被实现的。

多线程的优缺点

多线程的优点

1)无需跨进程边界;程序逻辑和控制方式简单;

2)所有线程可以直接共享内存和变量等;

3)线程方式消耗的总资源比进程方式好。

多线程的缺点

1)每个线程与主程序共用地址空间,受限于2GB地址空间;

2)线程之间的同步和加锁控制比较麻烦;一个线程的崩溃可能影响到整个程序的稳定性;

3)到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows Server 2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;

4)线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU 。

五.用户线程和内核级线程

用户级线程任何应用程序都可以通过使用线程库设计成多线程程序。线程库是用于用户级线程管理的一个例程句,它包含用于创建和销毁线程的代码、在线程间传递消息和数据的代码、调度线程执行的代码以及保存和恢复线程上下文的代码。该情况下所有活动发生在用户空间中,并且发生在一个进程中,而内核并不知道这些活动。内核继续以进程为单位进行调度。 

同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。

内核级线程有关线程管理的所有工作都是由内核完成的。 

内核支持线程执行系统调用指令时,只导致该线程被中断

以下是用户级线程和内核级线程的区别:

(1)内核支持线程是OS内核可感知的,而用户级线程是OS内核不可感知的。

(2)用户级线程的创建、撤消和调度不需要OS内核的支持,是在语言(如Java)这一级处理的;而内核支持线程的创建、撤消和调度都需OS内核提供支持,而且与进程的创建、撤消和调度大体是相同的。

(3)用户级线程执行系统调用指令时将导致其所属进程被中断,而内核支持线程执行系统调用指令时,只导致该线程被中断。

(4)在只有用户级线程的系统内,CPU调度还是以进程为单位,处于运行状态的进程中的多个线程,由用户程序控制线程的轮换运行;在有内核支持线程的系统内,CPU调度则以线程为单位,由OS的线程调度程序负责线程的调度。

(5)用户级线程的程序实体是运行在用户态下的程序,而内核支持线程的程序实体则是可以运行在任何状态下的程序。

内核线程的优点:

(1)当有多个处理机时,一个进程的多个线程可以同时执行。

缺点:

(1)由内核进行调度。

用户进程的优点:

(1) 线程的调度不需要内核直接参与,控制简单。

(2) 可以在不支持线程的操作系统中实现。

(3) 创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。

(4) 允许每个进程定制自己的调度算法,线程管理比较灵活。

(5) 线程能够利用的表空间和堆栈空间比内核级线程多。

(6) 同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题。

缺点:

(1)资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用

六.多线程和多进程在pytho的使用

python多进程使用流程:

Python 要进行多进程操作,需要用到muiltprocessing库,其中的Process类跟python线程模块threading中的Thread类很相似。

python进行多进程的编程:

# Desc: python的多进程操作
import time
from multiprocessing import Process

count = 0

#进程调用的方法
def run():
    # 定义一个全局变量count,代表一个计数器,每个进程都会对count进行加1操作
    global count
    for i in range(1000000):
        count += 1
    print('进程结束:', count)

if __name__ == '__main__':
    # 开始时间
    start = time.time()
    # 创建进程process_list
    process_list = []
    # 创建10个进程,并启动,将进程对象存储到process_list中
    for i in range(10):
        p = Process(target=run)
        process_list.append(p)

    # 等待所有进程结束
    for p in process_list:
        p.start()
    for p in process_list:
        p.join()

    # 结束时间
    end = time.time()
    print('时间:', end-start)
    # 打印count,看看是否是10000000
    print(f'count:', count)

如下运行结果:

python多线程使用流程:

Python提供两个模块进行多线程的操作,分别是thread和threading,

前者是比较低级的模块,用于更底层的操作,一般应用级别的开发不常用。

python进行多线程的编程:

# Desc: python的多线程操作
import time
from threading import Thread

count = 0

#线程调用的方法
def run():
    # 定义一个全局变量count,代表一个计数器,每个线程都会对count进行加1操作
    global count
    for i in range(1000000):
        count += 1
    print('线程结束:\n', count)

if __name__ == '__main__':
    start = time.time()
    process_list = []
    for i in range(10):
        p = Thread(target=run)
        process_list.append(p)

    for p in process_list:
        p.start()
    for p in process_list:
        p.join()

    end = time.time()
    print("time",end-start)
    print("count:",count)

运行结果如下:

线程和进程两者的区别

  • 线程必须在某个进程中执行。

  • 一个进程可包含多个线程,其中有且只有一个主线程。

  • 多线程共享同个地址空间、打开的文件以及其他资源。

  • 多进程共享物理内存、磁盘、打印机以及其他资源。

七、python编程线程中的GIL锁

其他语言,CPU 是多核时是支持多个线程同时执行。但在 Python 中,无论是单核还是多核,同时只能由一个线程在执行。其根源是 GIL 的存在。

GIL 的全称是 Global Interpreter Lock(全局解释器锁),来源是 Python 设计之初的考虑,为了数据安全所做的决定。某个线程想要执行,必须先拿到 GIL,我们可以把 GIL 看作是“通行证”,并且在一个 Python 进程中,GIL 只有一个。拿不到通行证的线程,就不允许进入 CPU 执行。

而目前 Python 的解释器有多种,例如:

  • CPython:CPython 是用C语言实现的 Python 解释器。 作为官方实现,它是最广泛使用的 Python 解释器。

  • PyPy:PyPy 是用RPython实现的解释器。RPython 是 Python 的子集, 具有静态类型。这个解释器的特点是即时编译,支持多重后端(C, CLI, JVM)。PyPy 旨在提高性能,同时保持最大兼容性(参考 CPython 的实现)。J

  • ython:Jython 是一个将 Python 代码编译成 Java 字节码的实现,运行在JVM (Java Virtual Machine) 上。另外,它可以像是用 Python 模块一样,导入 并使用任何Java类。IronPython:IronPython 是一个针对 .NET 框架的 Python 实现。它 可以用 Python 和 .NET framewor k的库,也能将 Python 代码暴露给 .NET 框架中的其他语言。

GIL 只在 CPython 中才有,而在 PyPy 和 Jython 中是没有 GIL 的。
每次释放 GIL锁,线程进行锁竞争、切换线程,会消耗资源。这就导致打印线程执行时长,会发现耗时更长的原因。

GIL锁释放的条件

执行时间过长;

线程自己操作IO。GIL锁会在执行阻塞IO操作时被释放。这是因为在等待IO操作完成的过程中,Python线程不需要持有GIL,也不需要执行Python字节码

GIL锁常见解决方案

第一种:(不推荐)

换个解释器运行程序,由于只有Cpython中才有GIL锁,可以换成Pypy或者Jython的解释器,Jython是java写的python解释器。

第二种:(推荐)

使用多进程替换多线程(mutilprocessing是一个多进程模块,开多个进程,每个进程都是一个GIL锁,就相当于多线程来用了)

第三种:(推荐)

使用python语言的特性(胶水)

让子线程部分用c语言来写,其他部分使用python。(就相当于那部分代码绕过了cpython解释器)

八、总结

以上就是分析详解python多线程与多进程区别的详细内容。

总之,使用python在线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源的管理和保护;而进程正相反。

需要了解更多请访问如下网站:

https://www.jianshu.com/p/fd3927cd6ee7多线程和多进程的区别(并行编程 1)

https://blog.csdn.net/bb8886/article/details/132045825Python--GIL锁

https://www.jb51.net/article/222078.htm#_lab2_0_2 分析详解python多线程与多进程区别