Python并发编程

作者:

在

基础概念

CPU密集型和IO密集型

cpu密集型任务的特点是:持续使用cpu,持续不断的运行。例如压缩与解压,代码编译,视频编解码等。这些任务只要开始,cpu占用曲线就居高不下。

IO密集型任务特点是:大量时间用于等待,cpu处理时间少。例如用户输入,文件I/O,网络I/O,数据库I/O等。cpu占用曲线很低,偶尔有尖峰。

那如何解决这个问题?

并发和并行

并发是物理上一个cpu交替做,一个cpu通过控制执行时间即可做到(时间片轮转算法)

比如咖啡店做咖啡,一个咖啡机,轮流做拿铁,卡布奇诺,美式,对于客户来说,看起来几乎在同时处理

并行是物理上多个cpu同时做,因此必须多核CPU才能实现并行

比如咖啡店做咖啡,3台机器,分别做拿铁,卡布奇诺,美式,互不干扰,同时完成

image-20260608074750199

进程到底有哪些东西构成?线程有哪些东西构成?

进程在操作系统内核角度的描述

组成部分 内容
pid 进程唯一编号
进程状态 就绪,运行,阻塞
程序计数器(pc) 执行到哪条指令了
代码段 程序的指令
数据段 全局变量、静态变量
堆 动态分配的对象(malloc)
栈 函数调用链 + 局部变量
文件描述符表(fd) 打开的文件和网络连接file:[0,1,2,3],标准输入,标准输出和标准错误是哪几个文件描述符

线程相较于进程独立的东西(TID,pc,线程状态,栈区)剩下的数据段,代码段,堆等都与进程共享。

在 Linux 内核的代码里,其实根本没有线程这个数据结构。无论是进程还是线程,在内核中都叫“任务”(Task),都用task_struct结构体来表示。

那进程和线程的区别体现在哪?—— 体现在“共享资源的多少”上。

可以把 task_struct理解为一张“任务身份证”,每个执行流都有一张。区别在于:

一个进程(通常指只有主线程的情况):这张身份证指向的所有资源(内存、文件等)都是独占的。

一个线程:这张身份证指向的资源,大部分和同组的其他身份证共享

这就是为什么 Linux 的线程被称为轻量级进程的原因创建线程时,内核不需要像创建进程那样复制一大堆资源,只需要新建一个 task_struct,然后让它指向现有资源即可。

根本区别:操作系统资源分配的最小单位是进程,CPU独立调度和执行的最小单位是线程。

共享关系:进程间内存相互独立,不可直接访问;同一进程下的线程,共享该进程的堆,文件等资源。

切换开销:进程更大,线程更小,只要保存少量寄存器和栈信息。

进程转换状态图

见课件部分,核心三态,补充两态

线程和进程一样

就绪缺的是cpu时间,阻塞缺的是某个资源或者事件(磁盘,网络,用户输入)

image-20260608210122054

同步和异步、阻塞与非阻塞

概念 描述的对象 描述的是什么 例子
阻塞/非阻塞 调用者(线程/进程)的状态 调用者在等待结果时,自己还能不能干别的事 (阻塞)站在排队队伍里,不能刷手机,不能看书,整个人处于“石化”和等待状态,只能干等。(非阻塞)在等餐时,你一直在刷手机、背单词或者和同学聊天。虽然你还在等,但你并没有被“冻结”,然后自己轮询看看好没好(polling)
同步/异步 被调用者(任务/IO,通常是函数)的通知方式 任务完成时,是主动通知调用者,还是需要调用者主动去问 (同步)你在打饭窗口点了小笼包,阿姨正在蒸。你就在窗口一直盯着阿姨,直到阿姨把小笼包递给你,你才离开(异步) 你在取餐窗口点了黄焖鸡,阿姨给了你一个会震动的取餐号牌(回调机制)。你拿到号牌就可以离开窗口了,等黄焖鸡好了,号牌一震动,你再去取

阻塞/非阻塞 = 我(调用者)还能不能动

同步/异步 = 它(被调用者)会不会叫我

排列组合

组合 调用者状态 通知方式
同步阻塞 等结果时不能动 调用者主动等
同步非阻塞 立即返回,但要轮询 调用者主动问
异步(非阻塞) 立即返回,不用轮询 被调用者主动通知(回调)

==总结==:

现代操作系统运行程序时,基本都是以并发方式进行的。这里的“并发”指的是在宏观上多个程序同时向前推进,但在微观(单核 CPU)上其实是交替执行,每个时刻只有一个程序占用 CPU。

为了更精确地说明,有必要区分两个概念:

  • 并发:多个任务在同一个时间段内交替执行(逻辑上同时)。单核 CPU 通过时间片轮转实现并发。
  • 并行:多个任务在同一时刻同时执行(物理上同时)。只有多核 / 多 CPU 才能实现真正并行。

为什么操作系统运行程序基本都是并发的?

  1. CPU 数量有限 即使是普通的多核 CPU(例如 8 核),同时运行的线程数往往远超 8 个。操作系统必须通过时分复用让所有线程都有机会运行。
  2. 提高资源利用率 一个程序等待 I/O(磁盘、网络、键盘)时,CPU 会空闲。并发调度允许此时运行另一个程序,避免 CPU 空转。
  3. 用户交互需求 现代操作系统需要同时响应鼠标、键盘、网络包、后台任务等,必须用并发来提供流畅体验(例如边听歌边写文档)。
  4. 进程 / 线程模型本身就是为并发设计的 操作系统提供进程、线程作为基本执行单元,调度器负责在它们之间快速切换(通常每秒数十到数千次),宏观上就像“同时运行”。

并发是如何实现的

​ 核心机制是抢占式多任务(现代操作系统普遍采用):

​ 时间片轮转:每个线程获得一个很短的 CPU 时间片(通常 10~100 ms)。时间片用完或主动阻塞(如 sleep、wait I/O)时,触发调 度。

​ 时钟中断:硬件定时器周期性中断 CPU,强制剥夺当前线程,并交由调度器选择下一个线程。

​ 优先级调度:重要任务(如实时音频)可抢占普通任务的 CPU 时间。

​ 这样,即使你只打开了一个文本编辑器,后台依然有数十个系统服务、驱动程序、守护进程在并发执行。


​ 并发 ≠ 并行 单核时代:严格并发(交替执行),没有真正意义的并行。

多核时代:可以同时存在并发 + 并行。 例如 4 核 CPU 上运行 100 个线程:同一时刻有 4 个线程在并行运行,整体上仍然是 100 个线程并发运行。

面试题

一、同步非阻塞 vs 异步回调 核心对比

维度 同步非阻塞 异步回调
操作完成通知方式 调用方主动轮询 系统自动调用回调
CPU开销 有轮询开销(可结合epoll优化) 无轮询开销
典型代表 epoll + 非阻塞socket(Reactor) Node.js、io_uring(Proactor)
适用场景 活跃连接数可控、需精细控制 极高并发、突发流量、长尾敏感

二、关键追问答案

  1. 谁性能更好? 理论异步更优,但实际epoll(同步非阻塞)已足够高效。极高IOPS下io_uring(真正异步)胜出。
  2. epoll是同步非阻塞还是异步? 同步非阻塞。事件就绪后需应用主动调用read/write完成I/O。
  3. 如何解决回调地狱? Promise链式调用 → async/await语法糖(本质仍基于回调,但扁平化)。
  4. 工作中如何选?
    • 高性能中间件(网关、代理)→ 同步非阻塞+epoll(如Nginx)
    • 业务密集型应用 → 异步回调+async/await(如Node.js、Pythonasyncio)
    • 低并发简单场景 → 同步阻塞最省事

三、面试结语(核心结论)

同步非阻塞需要应用主动轮询或依赖事件通知,实际读写由用户线程完成,属于同步范畴;异步回调由系统/线程池完成后反向通知应用,属于异步范畴。 现代开发中,异步回调配合async/await是主流范式,但同步非阻塞(Reactor)在底层基础库中仍广泛使用。


python多进程

1. 进程创建和基础结构

进程核心方法:

start——启动进程

join——主进程等待子进程终止。默认主进程会自动等待所有子进程结束才会退出

​ os. getpid( )——获取进程id

os.getppid()——获取父进程id

import os
from multiprocessing import Process
from time import sleep

def func(name):
    print(f"以子进程方式运行{name}")
    print(f"子进程进程号={os.getpid()}")
    print(f"子进程中父进程进程号={os.getppid()}")
    sleep(10)

# linux/mac系统是fork机制,windows用的是spawn机制,子进程为了获取代码,必须把Python文件从头导入一边
if __name__ == "__main__":
    p = Process(target=func, args=("test",))  # 单元素元组需要加,否则和字符串混淆了
    p.start()  # 一定用start才能开启新进程,run只是在当前线程直接运行函数
    # p.join()   # 等待子进程结束
    print(f"父进程进程号={os.getpid()}")
    print("主进程运行结束")

image-20260609102004707

image-20260609102114400

image-20260609102552247

2. 多进程同时创建

import time
from multiprocessing import Process 

def func(name): 
    print("hello %s" % name)
    time.sleep(1)

if __name__ == "__main__":
    for i in range(20):
        p = Process(target=func, args=(f"{i}",))
        p.start()
        # p.join()  # 如果在每个start之后,都join等待子进程,便可以保证顺序运行,但是,这无法发挥多进程优势
    print("主程序结束,等待子进程....")  # 运行结果发现,进程调度确实不是顺序的,由操作系统自己决定

image-20260609105708390

3. 跟随进程

随着主进程结束,子进程就被立即强行终止,因此此类进程并不保证里面的任务能跑完。只要主进程代码执行完,daemon=True的子进程立即杀死。

import time
from multiprocessing import Process


def foo():
    print(123)
    time.sleep(1)
    print("end123")  # 父进程代码执行结束,所以这里不会输出


def bar():
    print(456)
    time.sleep(3)
    print("end456")


if __name__ == "__main__":
    p1 = Process(target=foo, daemon=True)  #设置为跟随,主进程退出,p1立即被强制终止
    p2 = Process(target=bar)
    p1.start()
    p2.start()
    time.sleep(0.1)
    print("main-------")

适合心跳、日志刷写这种丢了也没啥事的任务

操作系统中的守护进程不是python中的这个含义,可以去检索一下,linux操作系统守护进程如何创建,如何查询,如何关闭

4. 进程间通信(IPC)

Value共享

from multiprocessing import Process, Value

def increment(num):
    num.value += 1
    print(num.value)

if __name__ == "__main__":
    counter = Value("i", 0)  # 'i' 表示整数类型
    processes = [Process(target=increment, args=(counter,)) for _ in range(30)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()
# 每次执行的结果都会不一样

Array共享

from multiprocessing import Array, Process

def increment(arr):
    for i in range(len(arr)):
        arr[i] += 1
    print(arr[:])

if __name__ == "__main__":
    arr = Array("i", [0, 0, 0])  # 'i' 表示整数类型
    processes = [Process(target=increment, args=(arr,)) for _ in range(30)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

5. 进程间同步

​ 1. 互斥锁——Lock

from multiprocessing import Lock, Process, Value

def increment(num, lock):
    lock.acquire()
    num.value += 1
    print(num.value)
    lock.release()
# 使用with可以简化,自动获取和释放锁
# def increment(num, lock):
#     with lock:
#         num.value += 1
#         print(num.value)

if __name__ == "__main__":
    lock = Lock()
    counter = Value("i", 0)  # 'i' 表示整数类型
    processes = [Process(target=increment, args=(counter, lock)) for _ in range(30)]
    for p in processes:
        p.start()
    for p in processes:
        p.join()

​ 2. 信号量

from multiprocessing import Process, Semaphore, Value

def increment(num, sem):
    # 1. 尝试获取信号量(拿钥匙),如果没有钥匙了,进程会在这里阻塞等待
    sem.acquire()

    try:
        # 2. 核心临界区:此时只有一个进程能进入这里,安全地修改数据
        num.value += 1
        print(num.value)
    finally:
        # 3. 无论如何,执行完后必须释放信号量(还钥匙),否则后面的进程会永远死锁
        sem.release()


if __name__ == "__main__":
    counter = Value("i", 0)  # 共享变量

    # 创建一个容量为 1 的信号量(相当于互斥锁,同一时间只允许 1 个进程访问)
    sem = Semaphore(1)

    # 将信号量 sem 作为参数传给子进程
    processes = [Process(target=increment, args=(counter, sem)) for _ in range(30)]

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

    print(f"最终结果: {counter.value}")  # 必定雷打不动输出 30

image-20260611110518099


==总结==:

image-20260609154902633

image-20260609155020628


image-20260609160146959


6. IPC-Manager对象

from multiprocessing import Manager, Process

def update_dict(shared_dict):
    shared_dict["common"] = shared_dict["common"] + 1
    print(shared_dict)

if __name__ == "__main__":
    with Manager() as manager:
        shared_dict = manager.dict()
        shared_dict["common"] = 0
        processes = [
            Process(target=update_dict, args=(shared_dict,)) for _ in range(30)
        ]
        for p in processes:
            p.start()
        for p in processes:
            p.join()

image-20260610214147267


7. IPC-Pipe管道

TODO:linux开发中,c语言调用linux系统api创建的管道怎么做的

from multiprocessing import Pipe

conn1, conn2 = Pipe()
conn1.send("Hello")
print(conn2.recv())
conn2.send("World")
print(conn1.recv())

image-20260610222005990

image-20260610222459304


8. IPC-队列

队列的特点是是先进先出FIFO,multiprocessing.Queue是Python中线程安全的阻塞队列,能自动避免数据竞争问题(消息队列)

import queue
from multiprocessing import Queue

q = Queue(3)

q.put("1")
q.put("2")
q.put("3")
# q.put(3)  # 队列已满,put被阻塞

try:
    q.put_nowait("4")  # 非阻塞方式:但会抛出异常
except queue.Full:
    print("队列已经满了")

print(q.full())
print(q.get())
print(q.get())
print(q.get())
print(q.get())  # 队列已空,get被阻塞

生成这消费者模型,生产环境手动使用这些api很少,一般直接使用中间件,消息队列,比如RabbitMQ和Kafka.这些中间件提供了消息不丢失,持久化等开箱即用功能。

在linux操作系统中,进程间通信有哪些方式?

image-20260611093229685

image-20260611093330731

image-20260611093422146

==总结==:

image-20260611094215677

image-20260611094411837

9. 进程池

​ 1. 同步执行

因为阻塞知道任务完成,所以多个任务串行,适合需要保证顺序的场景

from multiprocessing import Pool
import time

def worker(n):
    """模拟耗时任务"""
    time.sleep(n)
    return f"任务 {n} 完成"

if __name__ == '__main__':
    with Pool(4) as pool:
        print("开始执行...")

        # 同步执行,会阻塞直到完成
        result = pool.apply(worker, (2,))
        print(f"结果: {result}")  # 2秒后才输出

​ 2. 异步执行

apply_async立即返回,任务在后台执行,不阻塞主进程

from multiprocessing import Pool
import time

def worker(n):
    time.sleep(n)
    return f"任务 {n}"

if __name__ == '__main__':
    with Pool(4) as pool:
        # 提交多个异步任务
        results = []
        for i in range(5, 0, -1):
            result = pool.apply_async(worker, (i,))
            results.append(result)
            print(f"已提交任务 {i}")

        # 收集所有结果
        for i, result in enumerate(results):
            print(f"获取结果 {i+1}: {result.get()}")

异步的回调写法

import time
from multiprocessing import Pool

def worker(n):
    time.sleep(n)
    return n * 2

def callback(result):
    """成功时的回调函数"""
    print(f"任务完成,结果: {result}")


def error_callback(error):
    """失败时的回调函数"""
    print(f"任务失败: {error}")


if __name__ == "__main__":
    with Pool(4) as pool:
        # 添加回调函数
        result = pool.apply_async(
            worker, args=(2,), callback=callback, error_callback=error_callback
        )

        # 主进程可以继续做其他事
        print("主进程继续执行...")

        # 确保所有任务完成
        result.wait()

image-20260611125932897

image-20260611130425061


python多线程

全局解释器锁GIL

英文全称Global Interpreter Lock。CPython(官方 Python)的解释器内部有一把全局锁,==同一时刻只允许一个线程执行 Python 字节码==。它的存在是为了保护CPython的内存管理(引用计数)不出现竞态条件,是一个历史设计决策。两个线程在做纯计算时(cpu密集型任务)都需要持有 GIL 才能跑。它们只能轮流持有锁、交替运行,永远无法真正并行。更糟的是,线程切换本身还有开销,实际跑下来甚至比单线程还慢。

线程创建和多线程运行

import threading
from time import sleep

def func(name):
    print(f"以子线程方式运行{name}")
    sleep(10)

if __name__ == "__main__":
    t = threading.Thread(target=func, args=("test",))  # 创建线程对象
    t.start()  # 启动线程
    # t.join()   # 可选:等待子线程结束
    print("主线程运行结束")

image-20260611163421000

image-20260611163617115


数据竞争问题,因为线程可以共享进程的全局变量,因此可以直接通过全局变量进行数据传递,以下模拟一个数据竞争问题

import time
from threading import Thread

counter = 0


def increment():
    global counter
    for _ in range(1000):  # 每个线程1000次
        tmp = counter
        time.sleep(0.000001)  # 关键:在读和写之间切换
        counter = tmp + 1


threads = [Thread(target=increment) for _ in range(100)]  # 总共10万次
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"期望: 100000, 实际: {counter}, 丢失: {100000 - counter}")

使用锁限制读写,确保同一时间只有一个线程操作

import time
from threading import Lock, Thread

lock = Lock()
counter = 0


def increment():
    global counter
    with lock:
        for _ in range(1000):  # 每个线程1000次
            tmp = counter
            time.sleep(0.000001)  # 关键:在读和写之间切换
            counter = tmp + 1


threads = [Thread(target=increment) for _ in range(100)]  # 总共10万次
for t in threads:
    t.start()
for t in threads:
    t.join()

print(f"期望: 100000, 实际: {counter}, 丢失: {100000 - counter}")

image-20260611184804789


线程同步

线程同步方案与进程类似,知道有数据竞争问题是核心,与进程一样,使用锁、信号量可以解决。条件变量和事件不做要求。知道存在即可。

线程共享方案

​ 1. 共享全局变量+Lock

​ 2. 队列

import queue
import threading

q = queue.Queue(1)

def worker():
    data = q.get()
    print(f"收到: {data}")


threading.Thread(target=worker).start()
q.put("hello")
q.put("world")
q.put("!")

跟随线程

Thread(target=increment, daemon=True)

线程池

from concurrent.futures import ThreadPoolExecutor

def square(n):
    return n * n

# 使用with不用自己手动关闭线程池
# executor = ThreadPoolExecutor(max_workers=3)
# executor.shutdown(wait=True)
with ThreadPoolExecutor(max_workers=3) as executor:
    resultMap = list(executor.map(square, [1, 2, 3, 4, 5]))
    print(resultMap)
    resultSinge = []
    for task in [1, 2, 3, 4, 5]:
        r = executor.submit(square, task)
        resultSinge.append(r.result())
    print(resultSinge)
import time
from concurrent.futures import ThreadPoolExecutor

import requests

urls = [
    "https://www.baidu.com",
    "https://www.zhihu.com",
    "https://www.bilibili.com",
    "https://www.baidu.com",
    "https://www.zhihu.com",
    "https://www.bilibili.com",
    "https://www.baidu.com",
    "https://www.zhihu.com",
    "https://www.bilibili.com",
]


def fetch(url):
    print(f"开始下载: {url}")
    resp = requests.get(url, timeout=5, verify=False)
    print(f"完成下载: {url}, 大小={len(resp.content)}")
    return len(resp.content)


# ========== 多线程版本(使用 submit + shutdown)==========
print("=== 多线程版本 ===")
start = time.time()

# 创建线程池
executor = ThreadPoolExecutor(max_workers=5)

# 提交所有任务,保存 future 对象
futures = []
for url in urls:
    future = executor.submit(fetch, url)
    futures.append(future)

# 关闭线程池(不再接受新任务)
executor.shutdown(wait=True)  # wait=True 等待所有任务完成

# 获取结果
results = [future.result() for future in futures]

thread_time = time.time() - start
print(f"多线程耗时: {thread_time:.2f}秒n")

# ========== 单线程版本 ==========
print("=== 单线程版本 ===")
start = time.time()
results_single = [fetch(url) for url in urls]
single_time = time.time() - start
print(f"单线程耗时: {single_time:.2f}秒n")

# ========== 对比 ==========
print("=== 对比 ===")
print(f"多线程: {thread_time:.2f}秒")
print(f"单线程: {single_time:.2f}秒")
print(f"提升倍数: {single_time / thread_time:.1f}x")

==总结==:

image-20260611201356753

进程和线程的设计还是有区别的

image-20260611201931885

image-20260611202413998


python协程

单线程内做到并发执行。一个协程写法如同函数,只是执行的方式与普通函数不同

背景

假设我们要开发一个高并发的Web服务器,需要同时处理 10,000 个并发请求(C10K问题)。如果我们用“一台服务器 + 传统多线程(1个请求=1个线程)”的模型,会发生什么?

传统多线程的“三座大山”

内存撑爆: 在 64 位 Linux 系统中,一个线程的默认栈大小通常是 8MB(即便调小,最低一般也需要几十到几百KB)。10000个线程需要80G左右内存,硬件成本极高。

上下文切换(Context Switch)内耗: 线程是由操作系统内核调度的。当 10,000 个线程频繁切换时,操作系统光是保存和恢复 CPU 寄存器等,就会把 CPU 的算力消耗殆尽。CPU 都在忙着“办手续”,没空“干正事”。

线程竞争: 为了防止数据死锁或错乱,必须加锁(Lock),这会导致线程挂起,进一步加剧切换开销。

能不能有一种技术,既能享受异步I/O的高效,又能像写同步代码一样简单,而且轻量到一台机器能跑几十万个? 这就是协程(Coroutine)。

对比普通函数(1分钟)

普通函数:调用 → 执行 → return(结束)

协程函数:调用 → 执行 → await(暂停)→ 恢复 → 继续

核心语法

async def:定义协程函数

await:启动并运行协程函数,且等待直到拿到协程函数的返回值

asyncio.run():启动事件循环

import asyncio


async def asyncTask():
    print("hello world")
    return 1


async def main():
    a = asyncTask()      #  异步任务此时并没有执行,只是拿到了一个协程对象
    #a = create_task(asyncTask())
    result = await a     #  异步任务在此启动运行,并直到运行结束,把协程函数返回值赋值给result
    print(result)

asyncio.run(main())

==重点知识==

​ 1. await必须在async函数中

​ 2. await可以启动并拿到一个async函数的执行结果

并发的关键操作

一定要让任务立即提交给队列,才能做到并发。

而常用的gather和create_task都可以做到

使用gather同时运行多个任务

多个任务同时执行,同时拿到所有任务的结果。如果任何一个任务失败,抛出异常;如果使用了asyncio.gather(task_a, task_b, return_exceptions=True)此参数,不会整体抛出异常,仅失败的抛出。

import asyncio
import time

start = time.time()


async def task(name, delay):
    await asyncio.sleep(delay)
    print(f"{name} 完成")
    return name


# async def taskErr():
#     raise RuntimeError("任务出错")


async def main():
    # 创建3个任务,分别延迟2秒、1秒、3秒
    # await task("A", 2)
    # await task("B", 1)
    # await task("c", 3)
    results = await asyncio.gather(task("A", 2), task("B", 1), task("C", 3))
    print(results)


asyncio.run(main())
end = time.time()
print(f"耗时: {end - start:.2f}秒")

image-20260612065109160

怎么识别异步任务?

1. async函数的能力范围

async函数只是声明了一个协程函数,并不是当中的操作都自动变成异步了。如果你依然使用了内置的同步api,那事件循环在执行async函数时,依然是阻塞状态。如下示例

import asyncio
import time
from asyncio.tasks import create_task


async def asyncTask():
    time.sleep(1)  # 你觉得会被放入队列的代码,所有的操作都需提供异步支持


async def main():
    start = time.time()
    a = create_task(asyncTask())
    b = create_task(asyncTask())
    c = create_task(asyncTask())
    await a
    await b
    await c
    end = time.time()
    print(f"Total took {end - start} seconds")

asyncio.run(main())

2. 如何识别异步函数呢

一般一个库函数支持异步,会在文档中声明支持async/await

常用的功能异步库

网络请求: httpx

import asyncio
import time

import httpx


async def task():
    client = httpx.AsyncClient()
    start = time.time()
    response = await client.get("https://www.baidu.com")
    end = time.time()
    print(f"一次请求所花时间{end - start}秒")
    print(response.status_code)
    await client.aclose()


async def main():
    tasks = []
    for _ in range(15):
        tasks.append(asyncio.create_task(task()))
    for t in tasks:
        await t


mainStart = time.time()
asyncio.run(main())
mainEnd = time.time()
print(f"总耗时{mainEnd - mainStart}秒")

文件读写: aiofiles

那如果实在要在写成函数中使用同步api怎么办呢?使用run_in_executor包裹一层

import asyncio
import time
from asyncio.tasks import create_task

from concurrent.futures import ThreadPoolExecutor
executor = ThreadPoolExecutor()

async def asyncTask():
    loop = asyncio.get_event_loop()
    # 线程池封装,其实削足适履了,就用不了协程特性了
    await loop.run_in_executor(executor, time.sleep, 1)


async def main():
    start = time.time()
    a = create_task(asyncTask())
    b = create_task(asyncTask())
    c = create_task(asyncTask())
    await a
    await b
    await c
    end = time.time()
    print(f"Total took {end - start} seconds")


asyncio.run(main())

image-20260612072640235

image-20260612073116511

==总结==:

image-20260612063017565

image-20260612063141099

image-20260612063204192

image-20260612063407707

==本质总结==:

image-20260612074002433

image-20260612074138353

协程同步

见课件

课程结尾

​ 1. 希望大家自己学习git,配合国内的gitee.com学会代码管理和分支管理

==全部总结==:

image-20260612075119542