python中的进程线程和协程

news2024/10/5 13:38:19

目录

  • 进程(Process)
    • 多进程代码实例
  • 线程(Thread)
    • 多线程存在原因及其缺点
    • 多线程代码实例
  • 协程(Coroutine)
    • 协程的优点
    • 协程代码实例
  • 进程、线程和协程适合的任务性质和环境
    • 多进程更适合的场景
    • 多线程更适合的场景
    • 协程更适合的场景

在 Python 编程中,进程、线程和协程是实现并发和并行执行任务的三种主要机制。它们之间的关系如下图所示:

在这里插入图片描述
下面是它们的简要概述,以及在 Python 中与它们相关的内容。

进程(Process)

操作系统对正在运行程序的抽象,这个就是进程(process)。

比如运行一个 web 浏览器,一个 text 文本,都是运行的一个一个进程。

有的人说:进程是程序运行资源的集合。进程是系统资源分配的最小单位等等。

从静态的角度来说,进程确实是运行程序的各种资源集合。
在这里插入图片描述

进程是操作系统分配资源并执行程序的基本单位。每个进程拥有自己的内存空间、数据栈以及其他跟踪执行的辅助数据。进程之间的内存空间是隔离的,因此它们之间的通信需要使用进程间通信机制(如管道、信号、共享内存、套接字等)。

操作系统有多个程序运行,那么就有多个进程,如下所示简图

在这里插入图片描述

在 Python 中,你可以使用 multiprocessing 模块来创建进程、管理进程间的通信和同步。

  • multiprocessing.Process:创建一个进程。
  • multiprocessing.Queuemultiprocessing.Pipe:进程间通信。
  • multiprocessing.Pool:用于并行执行任务的进程池。
  • multiprocessing.Valuemultiprocessing.Array:进程间共享数据。
  • 还有很多其他同步原语,如 LockEventSemaphore 等。

多进程代码实例

下面我们展示下使用进程的代码和优势,下面代码是通过使用进程和不使用进程进行四个网页的读取下载,并在最后通过各自的运行时间来进行比较:

import requests
from multiprocessing import Pool
import time

urls = ['https://example.com', 'https://httpbin.org', 'https://github.com', 'https://google.com']

def download_page(url):
    print(f"Downloading {url}...")
    response = requests.get(url)
    print(f"Downloaded {url} with status code {response.status_code}")

# 串行下载网页并测量时间
def serial_download(urls):
    start_time = time.time()
    for url in urls:
        download_page(url)
    end_time = time.time()
    return end_time - start_time

# 并行下载网页并测量时间
def parallel_download(urls):
    start_time = time.time()
    with Pool(4) as pool:
        pool.map(download_page, urls)
    end_time = time.time()
    return end_time - start_time

if __name__ == '__main__':
    print("Starting serial download...")
    serial_time = serial_download(urls)
    print(f"Serial download completed in {serial_time} seconds.")

    print("\nStarting parallel download with multiprocessing...")
    parallel_time = parallel_download(urls)
    print(f"Parallel download completed in {parallel_time} seconds.")

    print("\nComparison:")
    print(f"Serial: {serial_time} seconds")
    print(f"Parallel: {parallel_time} seconds")

运行结果展示如下:

在这里插入图片描述

线程(Thread)

线程是进程中的执行序列,一个进程可以包含多个线程,它们共享进程的内存空间和资源。线程之间的通信因此更加容易,可以直接读写同一进程内的数据。然而,因为这种共享,线程安全成为一个需要注意的问题,需要使用锁和其他同步机制来保证数据的一致性。

《操作系统设计与实现》里说:在传统操作系统中,每个进程中只存在一个地址空间和一个控制流(thread)。然后,有些情况下,需要在相同地址空间中有多个控制流并行的运行,就像他们是单独的进程一样(只是他们共享相同的地址空间)。

这些控制流通常被称为线程(thread),有时也称为轻量级进程(lightweight process)。

尽管线程必须在进程中执行,但是线程和进程是可以分别对待处理的两个概念。进程用来集合资源,而线程是 CPU 调度的实体。线程给进程模型增加的是,允许在同一个进程环境中有多个执行流,这些执行流在很大程度上相对独立。也即是说,在进程中,程序执行的最小单位(执行流)是线程,可以把线程看作是进程里的一条执行流。

一个进程里可以有一条或多条线程。
在这里插入图片描述

在 Python 中,可以使用 threading 模块来创建和管理线程。

  • threading.Thread:创建一个线程。
  • threading.Lockthreading.RLock:线程锁。
  • threading.Eventthreading.Condition:线程同步。
  • threading.Semaphore:信号量机制。
  • threading.local:线程本地数据。

多线程存在原因及其缺点

在一个应用程序执行过程中,应用程序里可能会有多种事件执行。

而有些事件执行一段时间后可能会被阻塞。如果把应用程序执行事件分解成多个并行运行的线程,即可以让程序设计变得简单,如果有阻塞的,可以把这部分让出行换其他线程执行。

还有一个原因是:线程比进程更轻量级。所以线程比进程更加容易创建,销毁。

第三个跟第一个有点关系,是关于性能的,若多线程都是 CPU 密集型的,那么不能获取性能上增强。如果有大量计算和大量 I/O 处理,那么多线程就可以获取性能上的优势,因为允许多线程重叠执行。

多线程的缺点:

1、对于多线程来说,进程中的资源是共享的,所以会产生资源竞争。

2、当进程中的一个线程崩溃了,会导致这个进程里的其他线程也崩溃。所以有时多进程程序更好,一个进程崩溃不会导致其他进程也崩溃。

多线程代码实例

下面我们展示下使用线程的代码和优势,下面代码是通过使用线程和使用进程进行四个网页的读取下载,并在最后通过各自的运行时间来进行比较:

import requests
import threading
from multiprocessing import Pool
import time

# 网站列表
urls = [
    'https://example.com',
    'https://httpbin.org',
    'https://github.com',
    'https://google.com'
]

# 下载单个页面的函数
def download_page(url):
    print(f"Downloading {url}...")
    response = requests.get(url)
    print(f"Downloaded {url} with status code {response.status_code}")

# 使用多线程下载页面并测量时间
def threaded_download(urls):
    start_time = time.time()
    threads = [threading.Thread(target=download_page, args=(url,)) for url in urls]
    for thread in threads:
        thread.start()
    for thread in threads:
        thread.join()
    end_time = time.time()
    return end_time - start_time

# 使用多进程下载页面并测量时间
def multiprocessing_download(urls):
    start_time = time.time()
    with Pool(4) as pool:
        pool.map(download_page, urls)
    end_time = time.time()
    return end_time - start_time

if __name__ == '__main__':
    print("Starting threaded download...")
    threaded_time = threaded_download(urls)
    print(f"Threaded download completed in {threaded_time} seconds.")

    print("\nStarting multiprocessing download...")
    multiprocessing_time = multiprocessing_download(urls)
    print(f"Multiprocessing download completed in {multiprocessing_time} seconds.")

    print("\nComparison:")
    print(f"Threaded: {threaded_time} seconds")
    print(f"Multiprocessing: {multiprocessing_time} seconds")

代码运行结果展示:
在这里插入图片描述
通过这里的结果,我们可以看到线程比进程所用的时间更短,效率更高,

协程(Coroutine)

协程是建立在线程之上,一般是语言级别的 ”多线程“ 模型,比线程更加的轻量级。有的叫它微线程。它是完全运行在用户态里。协程是在线程之上在进行抽象,它需要线程来承载运行。一个线程可以有多个协程。

协程是一种轻量级的、协作式的并发机制。它允许在单个线程内执行多个任务,通过协作而不是抢占来进行任务切换。协程为异步编程提供了更直观和易用的形式,可以有效地用于 I/O 密集型和高级别的结构化并发任务。

在 Python中,可以使用 asyncio 标准库来创建和管理协程。

  • async def:定义一个协程函数。
  • await:在协程中等待另一个协程的结果。
  • asyncio.run():运行最高层级的协程入口点。
  • asyncio.create_task():调度协程的执行。
  • asyncio.Eventasyncio.Lock 等:用于协程的同步原语。
  • asyncio.Queue:用于协程间的消息传递。

除了上述内容外,第三方库如 geventgreenlet 也提供了对协程的支持和优化,但它们的工作方式与 asyncio 不同。

Python 中的协程特别适合编写异步I/O操作,如Web服务器、客户端库等,因为它们在等待网络响应或磁盘I/O等操作的过程中可以挂起函数执行,让出控制权,允许其他协程运行。

协程线程关系图:
在这里插入图片描述

协程的优点

1、协程栈很小,只有几KB,而线程栈是 1 M,对比起来,创建大量协程需要的内存更少。

2、协程的调度是语言提供的 runtime 来调度,是在用户空间直接调度,不需要在内核空间和用户空间来回切换,浪费效率。

3、能更好的利用 cpu 的多核,提高程序执行性能。

4、避免阻塞,如果协程所在的线程发生了阻塞,那么协程调度器可以把运行在阻塞线程上的协程,调度到其它没有发生阻塞的线程上,继续运行。

协程代码实例

下面我们展示下使用协程的代码和优势,下面代码是通过使用协程同线程、进程进行四个网页的读取下载,并在最后通过各自的运行时间来进行比较:

import time
import threading
import asyncio
import aiohttp
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor

urls = ['https://example.com', 'https://httpbin.org', 'https://github.com', 'https://google.com']

# 同步下载页面的函数(用于线程和进程)
def download_page(url):
    with aiohttp.ClientSession() as session:
        with session.get(url) as response:
            return f"Downloaded {url} with status code {response.status}"

# 使用多线程下载页面
def threaded_download(urls):
    with ThreadPoolExecutor(max_workers=4) as executor:
        executor.map(download_page, urls)

# 使用多进程下载页面
def multiprocessing_download(urls):
    with ProcessPoolExecutor(max_workers=4) as executor:
        executor.map(download_page, urls)

# 异步下载页面
async def async_download_page(session, url):
    async with session.get(url) as response:
        return f"Downloaded {url} with status code {response.status}"

async def async_download_all_pages():
    async with aiohttp.ClientSession() as session:
        tasks = [async_download_page(session, url) for url in urls]
        return await asyncio.gather(*tasks)

# 测量函数执行时间的装饰器
def timeit(method):
    def timed(*args, **kw):
        ts = time.time()
        result = method(*args, **kw)
        te = time.time()
        print(f"{method.__name__} executed in {(te - ts):.2f} seconds")
        return result
    return timed

@timeit
def measure_threaded():
    threaded_download(urls)

@timeit
def measure_multiprocessing():
    multiprocessing_download(urls)

@timeit
async def measure_asyncio():
    await async_download_all_pages()

# 顺序运行三种方法并比较时间
if __name__ == '__main__':
    measure_threaded()
    measure_multiprocessing()
    asyncio.run(measure_asyncio())

进程、线程和协程适合的任务性质和环境

多进程更适合的场景

1、CPU密集型任务:
对于计算密集型操作,多进程通常比多线程更好,这是因为每个进程有自己的GIL,能够在多核处理器上并行运行,实现真正的并行计算。

2、内存隔离和安全:
进程之间的内存是隔离的,所以对于需要高安全性或内存隔离的任务,使用多进程会更安全,可以防止数据泄露或污染。

3、大规模并发和稳定性:
对于需要很多并发执行单元,但其中一些可能会因为异常或必须被杀死的任务,进程可能是更好的选择,因为一个进程崩溃不会影响到其他进程,而线程崩溃可能会影响整个应用程序的稳定性。

多线程更适合的场景

1、I/O密集型任务:
如果任务主要是I/O密集型的,例如网络请求或文件读写操作,线程通常能够提供很好的性能,因为当一个线程等待I/O操作时,其他线程可以继续执行。在Python中,虽然全局解释器锁(GIL)限制了同一时刻只有一个线程执行Python字节码,但I/O密集型任务在等待数据时会释放GIL,允许其他线程运行。

2、上下文切换开销小:
线程比进程有更小的内存占用和更快的创建及上下文切换时间。进程需要更多的资源和时间来创建,因为每个进程有自己独立的地址空间,而线程则共享内存地址空间。

3、GIL的影响有限:
由于下载任务主要是在等待网络响应,这意味着大部分时间线程并不持有GIL。因此,即使是在CPython这样的环境中,线程也可能是高效的。

协程更适合的场景

1、更高的I/O效率——非阻塞I/O操作:
协程可以在I/O操作等待数据时挂起,并让出CPU控制权给其他协程。这样可以处理大量的并发网络I/O,非常适合开发高效的网络服务器和客户端。

2、轻量级任务管理:
相对于进程和线程,协程拥有更小的内存占用,因为任务之间共享内存空间并且上下文切换开销很小。这允许程序员创建数以万计的协程而不会大量耗费系统资源。

3、简化的异步编程:
使用协程库(如Python中的asyncio),可以用顺序的方式编写非阻塞代码,降低了异步编程的复杂性。

4、更好的调度控制:
协程的调度是在用户空间完成的,这给了程序员更大的灵活性去控制任务执行的顺序。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

定了!嫦娥六号任务计划5月3日发射 | 最新快讯

来源:央视新闻客户端 今天(5 月 1 日),国家航天局发布最新消息,经工程任务指挥部综合研判决策,探月工程四期嫦娥六号任务计划 5 月 3 日实施发射。 发射窗口是指适合火箭发射的时间范围。此次任务在综合考虑…

Devops部署maven项目

这里讲下应用k8s集群devops持续集成部署maven项目的流程。 failed to verify certificate: x509: certificate signed by unknown authority 今天在执行kubectl get nodes的时候报的证书验证问题,看了一圈首次搭建k8s的都是高频出现的问题。 couldn’t get curren…

Unity LensFlare 入门

概述 在项目的制作过程中,太阳光的使用一定是不可缺少的部分,但是如果想实现真实太阳光眼睛看到的镜头炫光效果,那这部分的内容一定不要错过喔,接下来让我们来学习这部分的内容吧! Hale(光环效果) Color:…

JavaEE_操作系统之进程(计算机体系,,指令,进程的概念、组成、特性、PCB)

一、冯诺依曼体系(Von Neumann Architecture) 现代的计算机, 大多遵守冯诺依曼体系结构 CPU 中央处理器: 进行算术运算和逻辑判断.存储器: 分为外存和内存, 用于存储数据(使用二进制方式存储)输入设备: 用户给计算机发号施令的设备.输出设备: 计算机个…

黑马头条Day02_app端文章查看,静态化freemarker,分布式文件系统minIO

文章目录 app端文章查看,静态化freemarker,分布式文件系统minIO1)文章列表加载1.1)需求分析1.2)表结构分析1.3)导入文章数据库1.3.1)导入数据库1.3.2)导入对应的实体类 1.4)实现思路1.5)接口定义1.6)功能实现1.6.1):导入heima-leadnews-article微服务&am…

Springboot+Vue项目-基于Java+MySQL的智慧校园管理系统(附源码+演示视频+LW)

大家好!我是程序猿老A,感谢您阅读本文,欢迎一键三连哦。 💞当前专栏:Java毕业设计 精彩专栏推荐👇🏻👇🏻👇🏻 🎀 Python毕业设计 &…

1700java进销存管理系统Myeclipse开发sqlserver数据库web结构java编程计算机网页项目

一、源码特点 java web进销存管理系统是一套完善的java web信息管理系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发,数据库为sqlser…

6.k8s中的secrets资源

一、Secret secrets资源,类似于configmap资源,只是secrets资源是用来传递重要的信息的; secret资源就是将value的值使用base64编译后传输,当pod引用secret后,k8s会自动将其base64的编码,反编译回正常的字符…

正点原子[第二期]Linux之ARM(MX6U)裸机篇学习笔记-6.5--I.MX6U启动方式

前言: 本文是根据哔哩哔哩网站上“正点原子[第二期]Linux之ARM(MX6U)裸机篇”视频的学习笔记,在这里会记录下正点原子 I.MX6ULL 开发板的配套视频教程所作的实验和学习笔记内容。本文大量引用了正点原子教学视频和链接中的内容。…

LeetCode 69—— x 的平方根

阅读目录 1. 题目2. 解题思路一3. 代码实现一4. 解题思路二5. 代码实现二 1. 题目 2. 解题思路一 二分查找法,对于整数 i ∈ [ 0 , x ] i \in [0,x] i∈[0,x],我们判断 i 2 i^2 i2 和 x x x 的关系,然后找到最后一个平方小于等于 x x x …

DRF权限组件源码分析

DRF权限组件源码分析 权限组件相关配置同认证组件 0 认证组件的两种返回值 有权限,返回True,程序正常进行无权限,返回False,程序抛出异常 1 单视图应用 2 多视图应用 3 单视图多视图结合 在drf中,默认优先去全局中…

java大学生兼职就业求职招聘管理系统hg241-vue+Springboot

大学生兼职中心系统是一个帮助学生正确安排课余时间的系统,同学们可以根据课程的多少来选择不同的兼职工作,而商家也可以从不同的学生中挑选适合的人选。既然是兼职,那么这些工作基本都是按照小时来计算工资的,也许不会很高&#…

【Linux】项目自动化构建工具make/makefile的简单使用

使用步骤 1) 编写 创建 makefile 文件 vim makefile用 vim 打开名为 makefile 的文件,存在该文件则打开编辑,不存在则创建并打开.在 makefile 文件中编写需要编译的文件 test:test.cppg -o test test.cpp第一行: 冒号左侧为编译后的可执行文件名,可以随便取. 冒号右侧为依赖…

【C++庖丁解牛】C++11---lambda表达式 | 包装器

🍁你好,我是 RO-BERRY 📗 致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 🎄感谢你的陪伴与支持 ,故事既有了开头,就要画上一个完美的句号,让我们一起加油 目录 1. lambda表达式1.1 C98中…

「C++ 内存管理篇 00」指针

目录 一、变量,变量名和指针 1. 什么是变量? 2. 变量名和指针 3. 使用指针获取数据 二、指针变量和数组变量 三、编译器对指针的等级有着严格的检查 四、指针的加减 1. 存放指针的变量的加减 2. 存放指针的变量的自增自减 3. 两个指针相减 一、变量&…

【前端】-【防止接口重复请求】

文章目录 需求实现方案方案一方案二方案三 需求 对整个的项目都做一下接口防止重复请求的处理 实现方案 方案一 思路:通过使用axios拦截器,在请求拦截器中开启全屏Loading,然后在响应拦截器中将Loading关闭。 代码: 问题&…

CD-PAN复合纳米纤维膜

CD-PAN复合纳米纤维膜可能是通过某种特定的方法,如溶剂热反应或水热反应等,将CdS纳米颗粒与PAN(聚丙烯腈)纳米纤维结合起来的复合材料。 这种复合纳米纤维膜可能会继承CdS的光电性质和PAN纳米纤维的机械性能,从而在某些…

Golang | Leetcode Golang题解之第61题旋转链表

题目: 题解: func rotateRight(head *ListNode, k int) *ListNode {if k 0 || head nil || head.Next nil {return head}n : 1iter : headfor iter.Next ! nil {iter iter.Nextn}add : n - k%nif add n {return head}iter.Next headfor add > …

R语言4版本安装mvstats(纯新手)

首先下载mvstats.R文件 下载mvstats.R文件点此链接:https://download.csdn.net/download/m0_62110645/89251535 第一种方法 找到mvstats.R的文件安装位置(R语言的工作路径) getwd() 将mvstats.R保存到工作路径 在R中输入命令 source(&qu…

Day27:阻塞队列、Kafka入门、发送系统通知、显示系统

阻塞队列BlockingQueue BlockingQueue 解决线程通信的问题。阻塞方法:put、take。 生产者消费者模式 生产者:产生数据的线程。消费者:使用数据的线程。 (Thread1生产者,Thread2消费者) 实现类 ArrayBlockingQueueLinkedBlockingQueuePr…