python进程,线程与协程

一、多任务的概念

**什么是多任务:**多任务是指在同⼀时间内执⾏多个任务

我们之前所写的程序都是单任务的,也就是说⼀个函数或者⽅法执⾏完成 , 另外⼀个函 数或者⽅法才能执⾏。要想实现多个任务同时执⾏就需要使⽤多任务。

多任务的最⼤好处是充分利 ⽤CPU资源,提⾼程序的执⾏效率

多任务的两种表现形式:

① 并发 ② 并⾏

二、并发与并行

1、并发

概念:在⼀段时间内交替去执⾏多个任务。

  • 多个任务在同一个时间段内交替执行

  • 在单核CPU上通过时间片轮转实现任务切换

  • 强调任务的分解和调度

例如:对于单核cpu处理多任务,操作系统轮流让各个任务交替执⾏,假如:软件1执⾏0.01秒,切换 到软件2,软件2执⾏0.01秒,再切换到软件3,执⾏0.01秒...... 这样反复执⾏下去, 实际上每个软 件都是交替执⾏的 . 但是,由于CPU的执⾏速度实在是太快了,表⾯上我们感觉就像这些软件都 在同时执⾏⼀样 . 这⾥需要注意单核cpu是并发的执⾏多任务的。

并发:任务数量大于CPU核心数

2、并⾏

**概念:**在⼀段时间内真正的同时⼀起执⾏多个任务。

  • 多个任务在同一时刻真正同时执行

  • 需要多核CPU支持

  • 强调任务的同时执行

对于多核cpu处理多任务,操作系统会给cpu的每个内核安排⼀个执⾏的任务,多个内核是真正的 ⼀起同时执⾏多个任务。这⾥需要注意多核cpu是并⾏的执⾏多任务,始终有多个任务⼀起执⾏。

并行:任务数量小于或等于CPU核心数

3、并发与并行的区别

特性 并发 并行
核心定义 多个任务在重叠的时间段内交替执行 多个任务在同一时刻同时执行
关键思想 任务切换 同时执行
硬件需求 单核CPU即可实现 必须依赖多核CPU或多台计算机
关注点 程序的设计与结构(处理多个任务的能力) 程序的执行与计算(加快任务完成的速度)
主要目的 提高系统的资源利用率 和响应能力 提高系统的计算速度 和吞吐量
关系 并发是并行的必要条件,但并发不一定并行。 并行是并发的真子集。
生活比喻 一个人(单核)同时做两件事: 边看电视边回微信,大脑在快速切换。 两个人(多核)同时做两件事: 一个人看电视,另一个人同时回微信。
编程模型 多线程、协程、异步IO、事件循环 多进程、GPU计算、分布式计算
优势 使I/O密集型应用不会阻塞,最大化利用CPU时间片 充分利用多核资源,大幅缩短计算密集型任务的时间
挑战 线程安全、竞态条件、死锁、数据同步 任务拆分、负载均衡、进程间通信、数据一致性

三、进程与多进程

1、程序中实现多任务的⽅式

在Python中,想要实现多任务可以使⽤ 多进程来完成。

2、进程的概念

进程(Process)是资源分配的最⼩单位,它是操作系统进⾏资源分配和调度运⾏的基本单位,通 俗理解:⼀个正在运⾏的程序就是⼀个进程。

例如:正在运⾏的qq , 微信等 他们都是⼀个进程。

注: ⼀个程序运⾏后⾄少有⼀个进程

  • 程序在执行时的一个实例(应用程序)

  • 拥有独立的内存空间和系统资源

  • 进程间相互隔离,安全性高

3、多进程的作⽤

图中是⼀个⾮常简单的程序 , ⼀旦运⾏hello.py这个程序 , 按照代码的执⾏顺序 , func_a函数执⾏ 完毕后才能执⾏func_b函数 . 如果可以让func_a和func_b同时运⾏ , 显然执⾏hello.py这个程序的 效率会⼤⼤提升 .

未使用多进程

使用了多进程

4、多进程完成多任务

① 导⼊进程包

import multiprocessing

② 通过进程类创建进程对象

进程对象 = multiprocessing.Process()

③ 启动进程执⾏任务

进程对象.start()

5、通过进程类创建进程对象

进程对象 = multiprocessing.Process(group \[, target=任务名 \[, name]])

参数说明:

参数名 说明
target 执行的目标任务名,这里指的是函数名(方法名)
name 进程名,一般不用设置
group 进程组,目前只能使用None

6、进程创建与启动的代码

边听音乐,边敲代码:

python 复制代码
# 导入多进程模块,用于创建和管理进程
import multiprocessing
# 导入时间模块,用于模拟耗时操作和计时
import time


def music():
    """任务函数:模拟听音乐"""
    for i in range(3):
        print('听音乐...')
        time.sleep(0.2)  # 模拟每次听音乐的耗时


def coding():
    """任务函数:模拟敲代码"""
    for i in range(3):
        print('敲代码...')
        time.sleep(0.2)  # 模拟每次敲代码的耗时


if __name__ == '__main__':
    # 创建执行music函数的进程
    music_process = multiprocessing.Process(target=music)
    # 创建执行coding函数的进程
    coding_process = multiprocessing.Process(target=coding)

    # 启动两个进程
    music_process.start()
    coding_process.start()

    # 等待两个进程都执行完毕
    music_process.join()
    coding_process.join()

    print("所有任务完成!")

7、进程执⾏带有参数的任务

Process(group \[, target \[, name \[, args \[, kwargs]]]])

参数说明:

参数名 说明 示例
args 以元组的方式给执行任务传参,表示调用对象的位置参数 args=(1, 2, 'anne')
kwargs 以字典方式给执行任务传参,表示调用对象的关键字参数 kwargs={'name': 'anne', 'age': 18}

案例:args参数和kwargs参数的使⽤

python 复制代码
# 导入多进程模块
import multiprocessing
# 导入时间模块
import time

def music(num):
    """
    模拟听音乐的任务
    参数:
    - num: int, 音乐播放的次数
    该函数通过循环模拟播放音乐,每次播放后暂停0.2秒。
    """
    for i in range(num):
        print('听音乐...')
        time.sleep(0.2)

def coding(count):
    """
    模拟编码任务
    参数:
    - count: int, 敲代码的次数
    该函数通过循环模拟敲代码的动作,每次敲代码后暂停0.2秒。
    """
    for i in range(count):
        print('敲代码...')
        time.sleep(0.2)

# 在主程序中启动多进程
if __name__ == '__main__':
    # 创建音乐进程,参数为播放音乐的次数
    music_process = multiprocessing.Process(target=music, args=(3,))
    # 创建编码进程,参数为敲代码的次数
    coding_process = multiprocessing.Process(target=coding, kwargs={'count': 3})
    
    # 启动音乐进程
    music_process.start()
    # 启动编码进程
    coding_process.start()
    
    # 打印主进程开始的信息
    print('主进程开始')
    
    # 使主进程暂停1秒,以模拟进程初始化或资源准备的时间
    time.sleep(1)
    
    # 等待音乐进程结束
    music_process.join()
    # 等待编码进程结束
    coding_process.join()
    
    # 打印主进程结束的信息
    print('主进程结束')

注意点:

  1. 入口点保护:必须使用 `if name == 'main':` 保护主程序入口

  2. 参数序列化:传递给进程的参数必须是可序列化的

  3. 进程间通信:使用 `Queue`、`Pipe` 等机制进行进程间数据交换

  4. 资源管理:及时调用 `join()` 等待进程结束,避免僵尸进程

四、线程与多线程

1、线程的概念

在Python中,想要实现多任务还可以使⽤多线程来完成。

  • 进程内的执行单元(函数,class类)

  • 同一进程内的线程共享内存空间(数据可以共享)

  • 是CPU调度的基本单位

2、为什么使⽤多线程?

进程是分配资源的最⼩单位 , ⼀旦创建⼀个进程就会分配⼀定的资源 , 就像跟两个⼈聊QQ就需要 打开两个QQ软件⼀样是⽐较浪费资源的 .

线程是程序执⾏的最⼩单位 , 实际上进程只负责分配资源 , ⽽利⽤这些资源执⾏程序的是线程 , 也 就说进程是线程的容器 , ⼀个进程中最少有⼀个线程来负责执⾏程序 。

同时线程⾃⼰不拥有系统 资源,只需要⼀点⼉在运⾏中必不可少的资源,但它可与同属⼀个进程的其它线程共享进程所拥有 的全部资源 。这就像通过⼀个QQ软件(⼀个进程)打开两个窗⼝(两个线程)跟两个⼈聊天⼀样 , 实 现多任务的同时也节省了资源

3、多线程完成多任务

多线程完成多任务

① 导⼊线程模块

import threading

② 通过线程类创建线程对象

线程对象 = threading.Thread(target=任务名)

③ 启动线程执⾏任务

线程对象.start()

参数名 说明
target 执行的目标任务名,这里指的是函数名(方法名)
name 线程名,一般不用设置
group 线程组,目前只能使用None

线程创建与启动代码

单线程案例

python 复制代码
# 单线程案例
import time

def music():
    for i in range(3):
        print('听音乐...')
        time.sleep(0.2)

def coding():
    for i in range(3):
        print('敲代码...')
        time.sleep(0.2)

if __name__ == '__main__':
    music()
    coding()

多线程案例

python 复制代码
# 多线程案例
import time
import threading

def music():
    """
    模拟听音乐的任务
    循环三次,每次打印听音乐的状态并暂停0.2秒
    """
    for i in range(3):
        print('听音乐...')
        time.sleep(0.2)

def coding():
    """
    模拟编码的任务
    循环三次,每次打印敲代码的状态并暂停0.2秒
    """
    for i in range(3):
        print('敲代码...')
        time.sleep(0.2)

if __name__ == '__main__':
    # 创建一个线程,用于播放音乐。通过threading.Thread类创建线程,music函数作为线程要执行的目标。
    music_thread = threading.Thread(target=music)
    # 创建一个线程,用于编码工作。同样使用threading.Thread类创建,coding函数作为线程要执行的目标。
    coding_thread = threading.Thread(target=coding)
    
    # 启动音乐线程和编码线程
    music_thread.start()
    coding_thread.start()
    
    # 等待音乐线程完成其任务
    music_thread.join()
    # 等待编码线程完成其任务
    coding_thread.join()
    
    print('程序结束')

线程执⾏带有参数的任务

参数名 说明
args 以元组的方式给执行任务传参
kwargs 以字典方式给执行任务传参
python 复制代码
# 导入time模块和threading模块
import time
import threading

def music(num):
    """
    模拟听音乐的函数
    参数:
    - num: int, 控制音乐播放的次数
    该函数通过循环播放音乐,并在每次播放时暂停0.2秒来模拟听音乐的行为。
    """
    for i in range(num):
        print('听音乐...')
        time.sleep(0.2)

def coding(count):
    """
    模拟编码的函数
    参数:
    - count: int, 控制编码操作的次数
    该函数通过循环模拟编码行为,并在每次编码时暂停0.2秒来模拟实际编码过程。
    """
    for i in range(count):
        print('敲代码...')
        time.sleep(0.2)

# 程序入口
if __name__ == '__main__':
    # 创建一个线程,负责播放音乐
    # 参数说明:
    # - target: 指定要执行的函数,这里是音乐播放函数
    # - args: 传递给函数的参数,这里使用元组(3,)作为示例
    music_thread = threading.Thread(target=music, args=(3,))
    
    # 创建另一个线程,负责编码任务
    # 参数说明:
    # - target: 指定要执行的函数,这里是编码函数
    # - kwargs: 传递给函数的关键字参数,这里指定count为3
    coding_thread = threading.Thread(target=coding, kwargs={'count': 3})
    
    # 启动音乐线程和编码线程
    music_thread.start()
    coding_thread.start()
    
    # 等待音乐线程完成其任务
    music_thread.join()
    # 等待编码线程完成其任务
    coding_thread.join()
    
    print('程序结束')

注意点

  1. GIL限制:受全局解释器锁限制,CPU密集型任务无法真正并行

  2. 线程安全:共享数据时需要使用锁机制保证线程安全

  3. 死锁风险:多个锁的使用需要注意死锁问题

  4. 资源竞争:需要合理使用同步原语(Lock、RLock、Semaphore等)

五、协程

1 协程定义与优势

协程(Coroutine)是⽤户态的轻量级线程,通过协作式多任务实现并发。

相⽐线程,协程的切换⽆需操 作系统调度,仅需保存寄存器上下⽂,因此效率更⾼。

核⼼优势:

  1. ⽆锁机制:避免多线程同步开销

  2. ⾼并发:单线程内处理数千级I/O密集型任务(如⽹络请求)

  3. 代码简洁:⽤同步语法写异步逻辑( async/await )

2 协程实现⽅式(以 asyncio 为核⼼)

基础语法

python 复制代码
import asyncio

# 定义一个异步协程函数
async def my_coroutine():
    """
    一个简单的协程函数,用于演示asyncio库的基本使用。
    此函数没有参数和返回值。
    """
    print("Start")
    await asyncio.sleep(1)  # 非阻塞式休眠
    print("End")

# 运行协程
asyncio.run(my_coroutine())  # Python3.7+ 推荐方式

事件循环与任务创建

python 复制代码
import asyncio

# 定义一个异步任务,该任务会在指定延迟后完成
async def task(name, delay):
    """
    异步任务函数,模拟一个需要时间完成的任务。
    参数:
    name: 任务名称,用于标识任务。
    delay: 延迟时间,任务完成前需要等待的时间(秒)。
    """
    await asyncio.sleep(delay)  # 模拟任务耗时
    print(f"{name} completed")

# 定义主协程,用于管理和执行其他协程任务
async def main():
    """
    主协程函数,负责调度和执行多个异步任务。
    """
    # 创建任务列表
    tasks = [
        asyncio.create_task(task("A", 2)),  # 创建任务A,延迟2秒完成
        asyncio.create_task(task("B", 1))   # 创建任务B,延迟1秒完成
    ]
    await asyncio.gather(*tasks)  # 并发执行所有任务

# 运行主协程
asyncio.run(main())

注:输出顺序:B → A(任务按完成时间排序)

3 核⼼模块与API

模块/方法 作用描述
asyncio.run() 启动事件循环的入口函数
asyncio.create_task() 将协程包装为任务对象
asyncio.gather() 并发执行多个协程
asyncio.sleep() 非阻塞式等待(替代 time.sleep)
asyncio.Queue 协程安全队列,用于生产者-消费者模型

4 其他协程库

  1. Gevent

基于 greenlet ,通过 monkey.patch_all() ⾃动切换协程:

python 复制代码
# 导入gevent的monkey模块,用于打补丁以支持协程
from gevent import monkey

# 导入gevent模块,用于创建协程
import gevent

# 为所有标准模块打上补丁,使其支持协程
# 包括socket、select、thread、time等模块的阻塞操作变为非阻塞
monkey.patch_all()

# 定义一个简单的任务函数,将在协程中执行
def task():
    print("Gevent task")

# 使用列表推导式创建3个协程任务
# gevent.spawn() 创建协程实例,每个实例执行task()函数
tasks = [gevent.spawn(task) for _ in range(3)]

# 一次性等待所有协程完成
# joinall()会阻塞当前协程直到所有任务完成
gevent.joinall(tasks)
  1. Greenlet ⼿动切换协程:
python 复制代码
# 导入greenlet模块,实现轻量级协程
from greenlet import greenlet

# 定义第一个协程函数
def test1():
    print(1)
    gr2.switch()  # 手动切换到gr2协程

# 定义第二个协程函数
def test2():
    print(2)
    # test2执行完毕后自动回到主协程

# 创建协程对象
gr1 = greenlet(test1)  # 基于test1函数创建协程
gr2 = greenlet(test2)  # 基于test2函数创建协程

# 启动协程系统
gr1.switch()  # 切换到gr1协程,开始执行test1函数

注意点:

  1. 异步环境:协程必须在异步环境中运行

  2. await关键字:调用异步函数时必须使用 `await` 关键字

  3. 阻塞操作:避免在协程中使用阻塞操作,应使用异步版本

  4. 异常处理:合理处理协程中的异常,避免影响整个事件循环

  5. 资源管理:正确管理协程的生命周期,及时取消不需要的任务

六、进程 vs. 线程 vs. 协程 总结对比表

性 进程 (Process) 线程 (Thread) 协程 (Coroutine)
基本定义 资源分配的基本单位, 程序的一次执行实例 CPU调度的基本单位, 进程内的一个执行流 用户态的轻量级线程, 由程序员控制的协作式任务
资源开销 大 (独立内存空间、PCB) 中等 (共享内存,但有独立栈、TCB) 极小 (通常在KB级别,无系统开销)
切换开销 大 (需要切换内存空间,CPU上下文) 中等 (需切换CPU上下文,由OS内核调度) 极小 (用户态切换,无内核参与)
数据共享 复杂 (需要IPC:队列、管道、共享内存等) 简单 (共享进程内存,但需注意线程安全) 极简单 (共享所有上下文变量)
并发能力 利用多核CPU (真正并行) 利用多核CPU (真正并行) 单线程内并发 (协作式并发)
创建数量 少(通常几十到上百个) 中(通常几百到上千个) 极多(可轻松创建数万甚至百万个)
隔离性/安全性 高 (一个进程崩溃不影响其他进程) 低 (一个线程崩溃可能导致整个进程崩溃) 高 (一个协程异常通常不影响其他协程)
控制者 操作系统内核 操作系统内核 程序员(用户态)
适用场景 CPU密集型 任务、 需要高安全隔离的任务 I/O密集型 任务、 需要共享数据的并发任务 高并发I/O 密集型任务、 大量网络连接、异步编程
编程复杂度 高(需处理进程间通信) 中(需处理线程同步和锁) 低(异步编程模型,逻辑清晰)
Python模块 multiprocessing threading asyncio
全局解释器锁(GIL)影响 无影响(每个进程有独立GIL) 受限制(同一进程线程只能有一个执行) 受限制(在同一个线程内运行)
相关推荐
霸道流氓气质3 分钟前
幂等性与 Upsert:防止重复操作产生副作用的技术
开发语言·php
牛奔14 分钟前
mise 管理多版本 Go 项目
开发语言·前端·chrome·后端·golang
huaweichenai27 分钟前
spring boot对接阿里短信
java·spring boot
波力海苔夹心脆67528 分钟前
C# 视觉检测实战:PLC 按钮触发 VisionPro 检测,OK 亮灯 / NG 灭灯(西门子 PLC,含界面显示与图片存档)
开发语言·经验分享·c#·自动化·视觉检测·.net
KING-WU51228 分钟前
Linux 工具之Make 与 Makefile
java·linux·服务器
2601_9628857240 分钟前
如何用 Python 计算 BBI 多空指标?
开发语言·python
马剑威(威哥爱编程)1 小时前
【AI全栈后端12-08】Spring Boot 用 MCP 统一接入内部系统:让 AI 接一次,全公司复用
java·人工智能·spring boot
顽疲1 小时前
从零用 Java 实现小红书 SpringBoot Vue UniApp(23)笔记话题多对多:一条笔记进多个话题池
java·vue.js·spring boot
老王爱玩车1 小时前
动态内存管理
c语言·开发语言·学习·面试
I'm Jie1 小时前
【开源】7 天,我用 Rust 复刻了 FinalShell!Rhost v1.0.0 发布
开发语言·rust·开源