线程与进程的区别、协程

1【线程与进程的区别、协程】

image-20240121155654411

【1】 进程跟线程

        进程(Process)和 线程(Thread)是操作系统的基本概念, 但是它们比较抽象, 不容易掌握。关于多进程和多线程,教科书上对经典的一句话“进程是资源分配的最小单位, 线程是CPU调度的最小单位。”

        线程是程序中一个单一的顺序控制流程。

        进程内一个相对独立的、可调度的执行的单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。

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

【2】线程与进程的区别可以归纳为:

  • 地址空间和其他资源(如打开文件):进程间互相独立, 同一进程的各线程间共享。某进程期内的线程在其他进程不可见。

  • 通信:进程间通信IPC, 线程间可以直接读写进程数据段 (如全局变量)来进行通信--------需要进程同步和互斥手段的辅助, 以保证数据的一致性。

  • 调度和切换: 线程上下文切换比进程上下文切换要快得多。

  • 在多线程OS中, 进程不是一个可执行行的实体。

2【多线程和多进程的比较】

image-20240120221937440

总结: 进程和线程可以类比为火车和车厢

  • 线程在进程下行进(单纯的车厢无法运行)

  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)

  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另一辆火车, 比如站点换乘)

  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)

  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更消耗资源)

  • 进程间不会相互影响,一个线程挂掉了将会导致整个进程挂掉(一列火车火车不会影响到另一列火车, 但是如果一列火车上的一节车厢着火了,将会影响到该躺火车的所有车厢)

  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,统一火车的车厢不能在行进的不同的轨道上)

  • 进程使用的内存地址可以上锁, 即一个线程使用某写共享内存是,其他线程必须等他结束, 才能使用这一块内存。 (比如火车上的洗手间)-“互斥锁(mutex)”

  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量(semaphore)”

3【(为什么有了GIL锁还要互斥锁)】

是一种CPython解释器中使用的机制, 它保证统一时刻只有一个线程执行Python字节码。 这个锁的存在是为了简化CPython 的实现,因为Cpython 在设计之初并不考虑多线程的执行。

【1】注意:

GIL(全局解释器)只存在于CPython解释器中,同时Python解释器的一个特定实现。其他编程语言和环境没有这个概念。

因此在这些环境中可以自由的使用多线程来实现并进行计算。

image-20240121155739573

【2】多线程环境中, Python虚拟机的执行方法:

  1. 设置GIL

  2. 切换到一个线程去执行

  3. 运行直至指定数量的字节码指令,或者线程主动让出控制(可以调用sleep(2))

  4. 把线程设置为睡眠状态

  5. 解锁GIL

  6. 再次重复以上所有步骤

image-20240121155804352

        在单核CPU上,数百次的间隔检查才会导致一次线程切换。在多核CPU上,存在严重的线程颠簸(thrashing)。而每次释放GIL锁,线程进行锁竞争、切换线程,会消耗资源。单核下多线程,每次释放GIL,唤醒的那个线程都能获取到GIL锁,所以能够无缝执行,但多核下,CPU0释放GIL后,其他CPU上的线程都会进行竞争,但GIL可能会马上又被CPU0拿到,导致其他几个CPU上被唤醒后的线程会醒着等待到切换时间后又进入待调度状态,这样会造成线程颠簸(thrashing),导致效率更低。

        在单核CPU上,由于只要一个核心执行代码,每次是否GIL后唤醒的线程都能够立即获取到GIL锁,因此可以实现无缝执行。

        然而,在多核CPU上,由于存在多个核心同时执行代码,当一个核心释放GIL锁后,其他核心上的线程会竞争获取GIL锁。虽然GIL可能会马上又被原来持有的核心获取到,但其他核心上被唤醒后的线程会进入待调度状态等待切换时间。这样的竞争和切换过程可能导致线程颠簸(thrashing),从而降低性能效率。

        简单的来说就是先到先的锁机制。单核可以实现无缝执行。多核则不行。

4【Python的多进程multiprocessing】

image-20240121155907247

【1】前言

        借助这个multiprocessing,你可以轻松完成从单进程到并发执行的转换。multiprocessing支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。

【2】multiprocessing常用组件及中功能

创建管理进程模块:

  • Process(用于创建进程)

  • Pool(用于创建管理进程池)

  • Queue(用于进程通信,资源共享)

  • Value,Array(用于进程通信,资源共享)

  • Pipe(用于管道通信)

  • Manager(用于资源共享)

同步子进程模块:

  • Condition(条件变量)

  • Event(事件)

  • Lock(互斥锁)

  • RLock(可重入的互斥锁(同一个进程可以多次获得它,同时不会造成阻塞)

  • Semaphore(信号量)

【进程】Process 用于创建进程

multiprocessing模块提供了一个Process类来代表一个进程对象。

在multiprocessing中,每一个进程都用一个Process类来表示。

构造方法: Process([group [, target [, name [, args [, kwargs]]]]])

  • group: 分组,实际上不适用, 值始终为None

  • target:表示调用对象,即子进程要执行的任务,你可以传入方法名

  • name:为子进程设定名称

  • args: 要传给target函数的位置参数,以元组方式进行传入

  • kwargs:要传给target函数的字典参数,以字典方式传入

  • daemon:一个布尔值,用于设置进程是否为守护进程,守护进程会在主进程结束时会自动退出

from multiprocessing import Process
import time
​
def work(name, delay):print(f'启动 {name} 进程')time.sleep(delay)print(f'结束 {name} 进程')
​
​
if __name__ == '__main__':
​processes = []
​for i in range(5):p = Process(target = work, args=(f'工作-{i}', i))p.name = f'工作 - {i}'p.daemon = Trueprocesses.append(p)
​p.start()p.join()
​print('工作结束!')
#         
# 启动 工作-0 进程
# 结束 工作-0 进程
# 工作结束!
# 启动 工作-1 进程
# 结束 工作-1 进程
# 工作结束!
# 启动 工作-2 进程
# 结束 工作-2 进程
# 工作结束!
# 启动 工作-3 进程
# 结束 工作-3 进程
# 工作结束!
# 启动 工作-4 进程
# 结束 工作-4 进程
# 工作结束!

实例方法:

  • star():启动进程,并调用子进程中的p.run()

  • work():执行进程的target函数,通常不需要手动调用,在start() 方法内部自动调用。

  • join([timeout]):等待进程结束,可选参数timeout为超时时间,如果设置了且超过时间到达时进程仍然未结束,则会返回。

  • is_alive():返回进程是否正在执行。

  • name:进程的名称,可以通过该属性获取或设置进程的名称。

  • pid:进程的ID, 可以通过该属性获取进程的ID。

# 实例方法
​
import multiprocessing
import time
​
def worker():print(f'worker 启动 进程 {multiprocessing.current_process().name}')time.sleep(2)print(f'worker 结束 进程 {multiprocessing.current_process().name}')
​
if __name__ == '__main__':p = multiprocessing.Process(target=worker, name='工作进程')p.start()
​print(f'这是进程的 {p.name} 是否执行 {p.is_alive()}')p.join()
​print(f'这是进程的 {p.name} 是否执行 {p.is_alive()}')
​print(f'结束进程{p.name} ')# 这是进程的 工作进程 是否执行 True
# worker 启动 进程 工作进程
# worker 结束 进程 工作进程
# 这是进程的 工作进程 是否执行 False
# 结束进程工作进程 

【3】Pool (创建进程池)

image-20240121160603125

        进程池是一种常见的并发编程工具,可以看作一个公共类或者公共资源,用于管理和复用进程。它提供了一种方便来创建、调度和执行多个进程, 并提供接口来提交任务、获取任务结果等操作。

        在使用进程池是, 开发者可以将需要执行的任务提交给进程池,进程池会自动分配可用的进程来执行任务。这样可以避免重复创建和销毁进程的开销,提高应用程序的性能和效率。

进程池的特点:

  • 可以预先创建一定数量的进程,减少创建和销毁进程的开销。

  • 提高接口来提交任务, 并将任务分配给空闲的进程来执行。

  • 可以限制同时执行的任务数量,以控制并发。

  • 提供接口来获取任务的执行的结果。

构造方法:

Pool([processes[, initializer[, initargs[, maxtasksperchild[, context]]]]])

  • processes:指定进程池中的进程数量,默认为 None,表示使用系统的 CPU 核心数;

  • initializer:一个可调用对象,用于在每个子进程启动时调用,用于初始化子进程;

  • initargs:一个元组,作为 initializer 函数的参数传递给子进程;

  • maxtasksperchild:指定每个子进程最多执行的任务数,默认为 None,表示不限制任务数;

  • context:指定一个上下文对象,用于创建子进程,默认为 None,表示使用默认的上下文对象。

进程池的实例方法:

  • apply(func, args=(), kwds={}):阻塞地提交一个任务,等待任务完成并返回结果。

    • func 是要执行的函数;

    • args 是传递给函数的位置参数,必须是一个元组;

    • kwds 是传递给函数的关键字参数,必须是一个字典。

  • map(func, iterable, chunksize=None):将一个可迭代对象中的所有元素应用到同一个函数上,并返回结果列表,按照可迭代对象中元素的顺序排列。

    • func 是要执行的函数;

    • iterable 是要处理的可迭代对象;

    • chunksize 是每个子进程处理的任务数量,默认为 None,表示使用默认的块大小。

  • imap(func, iterable, chunksize=None):与 map() 方法类似,但是它返回一个迭代器,可以一次获取一个结果。

    • func 是要执行的函数;

    • iterable 是要处理的可迭代对象;

    • chunksize 是每个子进程处理的任务数量,默认为 None,表示使用默认的块大小。

# 实例方法
import multiprocessing
​
def run(x):return x ** 2 ,f'跑了这些时间!'
​
if __name__ == '__main__':with multiprocessing.Pool(processes=4) as pool:res1 = pool.apply(run,(10,))
​res2 = pool.map(run,range(10))
​res3 = pool.imap(run, range(10))
​
​for i in res3:print(i)# (0, '跑了这些时间!')
# (1, '跑了这些时间!')
# (4, '跑了这些时间!')
# (9, '跑了这些时间!')
# (16, '跑了这些时间!')
# (25, '跑了这些时间!')
# (36, '跑了这些时间!')
# (49, '跑了这些时间!')
# (64, '跑了这些时间!')
# (81, '跑了这些时间!')

异步提交:

import multiprocessing
​
def my_func(arg):# 进程要执行的任务pass
​
if __name__ == "__main__":# 获取Context对象context = multiprocessing.get_context()
​# 创建进程池with context.Pool(4) as pool:# 异步提交任务给进程池result = pool.apply_async(my_func, (1,))# 阻塞主进程等待任务完成output = result.get()print(output)

同步提交:

import multiprocessing
​
def my_func(arg):# 进程要执行的任务pass
​
if __name__ == "__main__":# 获取Context对象context = multiprocessing.get_context()
​# 创建进程池with context.Pool(4) as pool:# 同步提交任务给进程池output = pool.apply(my_func, (1,))print(output)

【5】协程

综上所述:线程才是执行单位

协程的基础使用

        这是 python 3.7 里面的基础协程用法,现在这种用法已经基本稳定,不太建议使用之前的语法了。

【1】yield关键字

  • yield可以保存状态

    • yield的状态保存与操作系统的保存线程状态很像,但是yield是代码级别控制的,更轻量级。

  • send可以把一个函数的结果传给另一个函数

    • 以此实现单线程内程序之间的切换。

注意:单纯地切换反而会降低运行效率。

【2】创建协程的两种方法

生成器

def my_coroutine():while True:value = yield  # 接收从调用方传递过来的值print('收到:', value)​
# 创建生成器对象
coroutine = my_coroutine()​
# 启动生成器
next(coroutine)​
# 发送数据到生成器
coroutine.send('Hello')
coroutine.send('World')​
# 关闭生成器
coroutine.close()

asyncio库

import asyncio
​
async def my_coroutine():print('coroutine started')await asyncio.sleep(1)print('coroutine ended')
​
asyncio.run(my_coroutine())  # 使用 asyncio.run() 运行协程
# coroutine started
# coroutine ended

并发、并行、同步和异步

并发指的是 一个 CPU 同时处理多个程序,但是在同一时间点只会处理其中一个。并发的核心是:程序切换。

但是因为程序切换的速度非常快,1 秒钟内可以完全很多次程序切换,肉眼无法感知。

bingfa.jpg

并行指的是多个 CPU 同时处理多个程序,同一时间点可以处理多个。

并行.jpg

同步:执行 IO 操作时,必须等待执行完成才得到返回结果。 异步:执行 IO 操作时,不必等待执行就能得到返回结果。

yibu.jpg

协程,线程和进程的区别

        多进程通常利用的是多核 CPU 的优势,同时执行多个计算任务。每个进程有自己独立的内存管理,所以不同进程之间要进行数据通信比较麻烦。

        多线程是在一个 cpu 上创建多个子任务,当某一个子任务休息的时候其他任务接着执行。多线程的控制是由 python 自己控制的。 子线程之间的内存是共享的,并不需要额外的数据通信机制。但是线程存在数据同步问题,所以要有锁机制。

        协程的实现是在一个线程内实现的,相当于流水线作业。由于线程切换的消耗比较大,所以对于并发编程,可以优先使用协程。

协程的主要使用场景

协程的主要应用场景是 IO 密集型任务,总结几个常见的使用场景:

  • 网络请求,比如爬虫,大量使用 aiohttp
  • 文件读取, aiofile
  • web 框架, aiohttp, fastapi
  • 数据库查询, asyncpg, databases

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/535010.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

算法-差分-797.差分

题目 思路 本思路来自:AcWing 797. 差分 【c详细题解】 - AcWing 代码 n,m map(int,input().split()) alist(map(int,input().split())) a.insert(0,0) # 使下标从1开始 b[0 for _ in range(n5)] # b的列表开得足够大才不会超出index for i in range(1,n1):b[i]a…

算法-图的存储,图的转置,拓扑排序

1.图的存储 图用来对关系建模.图是节点和边构成的集合.节点反映图的元素集合,边反映图的元素集合中元素间的关系. 上述是由五个节点,三条边构成的结构.我们可以用图对其建模. 对由节点&#x…

选型|匠芯创工业级显示控制MCU

D13x系列微控制器 匠芯创D13x系列是一款基于RISC-V架构的高性能、国产自主、工业级跨界MCU,配备强大的2D图形加速、PNG解码、JPEG编解码引擎,具有丰富的屏接口,具有工业宽温、高可靠性、高开放性,可广泛应用于工业HMI、网关、串口…

武装部数字孪生三维电子沙盘系统研究与实现

深圳易图讯科技有限公司研发的(www.3dgis.top)武装部数字孪生三维电子沙盘系统是一种集成了先进信息技术和军事理论的创新系统,旨在通过三维可视化技术,实现战场环境的精确模拟和作战方案的快速生成。该系统利用三维建模技术,构建高度逼真的战…

C#进程说

C#中的进程可以通过System.Diagnostics命名空间下的Process类来控制和管理。可以通过以下步骤来创建和启动一个进程: 1. 引入System.Diagnostics命名空间。 using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using…

【相关问题解答2】bert中文文本摘要代码:结果输出为一些重复的标点符号和数字

【相关问题解答2】bert中文文本摘要代码 写在最前面问题1:tokenizer.py中encode函数,不能使用lower操作关于提问问题描述1一些建议1问题更新2:结果输出为一些重复的标点符号和数字一些建议21. 数据检查和预处理2. 模型和训练配置3. 过拟合和欠…

【问题总结】重写equalshashCode时需要的问题和避坑经验总结

知识点回顾 开发Java都知道,我们在使用POJO对象存储信息时,一定要重写 equals 方法和hashCode 方法 前者是为了解决当两个POJO对象执行equals时,对比具体数值信息,而不是直接对比对象本身。后者是为了解决因为在使用散列数据结构…

【Flutter 面试题】怎么理解Flutter的Isolate?并发编程

【Flutter 面试题】怎么理解Flutter的Isolate?并发编程 文章目录 写在前面解答补充说明完整代码示例说明 写在前面 🙋 关于我 ,小雨青年 👉 CSDN博客专家,GitChat专栏作者,阿里云社区专家博主,…

数据库系统概念(第二周 第一堂)

前言 本文的所有知识点、图片均来自《数据库系统概念》(黑宝书)、山东大学李晖老师PPT。不可用于商业用途转发。 回顾 上周最后一个知识点说到数据库三级模式结构,在这个结构里面我们设立了模式/内模式映像、内模式/外模式映像,主…

罐头鱼AI短视频矩阵获客|AI视频批量生成

罐头鱼AI传单功能操作说明,智能化提升您的视频营销效率! 在这个信息爆炸的时代,短视频已成为企业营销的重要方式之一。而为了更高效地进行视频营销,罐头鱼AI传单功能应运而生,为您提供全方位的视频管理和发布服务。 首…

3d模型渲染成线条怎么办?---模大狮模型网

将3D模型渲染成线条效果通常可以通过以下几种方法实现,具体取决于你使用的建模软件或渲染器: 使用渲染器的线条渲染功能: 许多渲染器(如Blender的Cycles渲染器、Maya的Arnold渲染器等)提供了线条渲染功能。通过在渲染器设置中启用线条渲染选…

生产环境是Linux,日志不好查?自己开发一个下载日志功能页面

有时候甲方爸爸的项目要部署内网,日志不能直接copy&#xff0c;还是linux系统。排查日志很不方便。 自己搞一个日志下载功能&#xff0c;如果是分布式的项目&#xff0c;还能把其他项目的日志也一起copy下来&#xff0c;来看。 public BiStateDTO<Object> logList(Requ…