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) 受限制(同一进程线程只能有一个执行) 受限制(在同一个线程内运行)
相关推荐
青鱼入云4 小时前
java面试中经常会问到的mysql问题有哪些(基础版)
java·mysql·面试
凤年徐4 小时前
C++类和对象(上):从设计图到摩天大楼的构建艺术
c语言·开发语言·c++·类和对象
凯哥Java4 小时前
适应新环境:Trae编辑器下的IDEA快捷键定制
java·编辑器·intellij-idea
從南走到北4 小时前
JAVA同城打车小程序APP打车顺风车滴滴车跑腿源码微信小程序打车源码
java·开发语言·微信·微信小程序·小程序
落日漫游4 小时前
K8s资源管理:高效管控CPU与内存
java·开发语言·kubernetes
PP东4 小时前
Pyhton基础之多继承、多态
开发语言·python
元直数字电路验证4 小时前
Jakarta EE课程扩展阅读(二)
开发语言·jakarta ee
wangmengxxw4 小时前
Maven的介绍及基本使用
java·maven