python3之多进程、多线程、协程、异步

多进程

进程就是一个程序在一个数据集上的一次动态执行过程。进程是操作系统分配存储空间的基本单位,每个进程都有自己的地址空间、数据栈以及其他用于跟踪进程执行的辅助数据;操作系统管理所有进程的执行,为它们合理的分配资源。一个进程可以通过 fork 或 spawn 的方式创建新的进程来执行其他的任务,新的进程有自己独立的内存空间,因此两个进程如果要共享数据,必须通过管道、信号、套接字等进程间通信机制来实现。

一个进程还可以拥有多个执行线程,简单的说就是拥有多个可以获得 CPU 调度的执行单元,这就是所谓的线程。由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核 CPU 系统中,多个线程不可能同时执行,因为在某个时刻只有一个线程能够获得 CPU。多个线程通过共享 CPU 执行时间的方式来达到并发的效果。

通过os模块的fork方法创建进程

多进程(multiprocessing):Unix/Linux操作系统提供了一个fork()函数,fork()函数调用时,操作系统自动把当前进程(父进程)复制了一份(子进程),然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的ID。
import os

print(f"Parent Process ({os.getpid()}) start")
try:
    pid = os.fork()
    if pid == 0:
        print(f"child process ({os.getpid()}) ,parent process ({os.getppid()})")
    else:
        print(f"parent process ({os.getpid()}) fork a child process ({pid}).")
except AttributeError as err:
    print(err)  # module 'os' has no attribute 'fork'

windows环境输出

Parent Process (4612) start
module 'os' has no attribute 'fork'

实例化Process类创建进程

windows 环境中多进程可以使用multiprocessing模块
创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
import os, time
from multiprocessing import Process


# 子进程要执行的代码
def run_proc(name):
    print(f"Run child process {name} ({os.getpid()})...")
    time.sleep(1)
    print(f"child process {name} ({os.getpid()}) done")


# 创建子进程时,只需要传入一个执行函数和函数的参数,
# 用start()方法启动,join()方法可以等待子进程结束后再继续往下运行,通常用于进程间的同步。
if __name__ == "__main__":
    print(f"Parent Process ({os.getpid()}) start...")
    p = Process(target=run_proc, args=("test",))  # 通过multiprocessing模块的Process类创建进程
    print("Child process will start.")
    p.start()  # 启动子进程
    p.join()  # 等待子进程执行完毕
    print("Child process end.")

输出

Parent Process (6304) start...
Child process will start.
Run child process test (20676)...
child process test (20676) done
Child process end.

继承Process类创建进程 

import os, time
from multiprocessing import Process


class TestProcess(Process):
    name = "测试进程"

    def __init__(self):
        super().__init__()

    def run(self):
        print(f"Run child process {self.name} ({ os.getpid()})...")
        time.sleep(1)
        print(f"child process {self.name} ({ os.getpid()}) done")


# 继承 Process 类自定义进程
if __name__ == "__main__":
    print(f"Parent Process ({os.getpid()}) start...")
    p = TestProcess()
    print("Child process will start.")
    p.start()
    p.join()
    print("Child process end.")

输出

Parent Process (27856) start...
Child process will start.
Run child process 测试进程 (21136)...
child process 测试进程 (21136) done
Child process end.

实例化Pool类创建进程池

import os, time, random
from multiprocessing import Pool


def run_proc(name):
    print(f"Run child process {name} ({os.getpid()})...")
    time.sleep(random.random())
    print(f"child process {name} ({os.getpid()}) done")


# 进程池
# Pool的默认大小是CPU的核数
# 调用join()之前必须先调用close(),调用close()之后就不能继续添加新的Process了。
if __name__ == "__main__":
    print(f"Parent Process ({os.getpid()}) start...")
    p = Pool(4)
    for i in range(5):
        p.apply_async(run_proc, args=(i,))
    print("Waiting for all subprocesses done...")
    p.close()  # 关闭进程池
    p.join()  # 等待所有子进程执行完毕
    print("All subprocesses done.")

输出

Parent Process (836) start...
Waiting for all subprocesses done...
Run child process 0 (19948)...
Run child process 1 (5084)...
Run child process 2 (14552)...
Run child process 3 (7928)...
child process 0 (19948) done
child process 2 (14552) done
Run child process 4 (19948)...
child process 1 (5084) done
child process 3 (7928) done
child process 4 (19948) done
All subprocesses done.

实例化ProcessPoolExecutor类创建进程池

from concurrent.futures import ProcessPoolExecutor
import os, time, random


def run_proc(name):
    print(f"Run child process {name} ({os.getpid()})...")
    time.sleep(random.random())
    print(f"child process {name} ({os.getpid()}) done")


# 进程池
if __name__ == "__main__":
    print(f"Parent Process ({os.getpid()}) start...")
    with ProcessPoolExecutor(max_workers=2) as pool:
        for i in range(0, 4):
            pool.submit(run_proc, i)
    print("All subprocesses done.")

输出

Parent Process (6564) start...
Run child process 0 (24312)...
Run child process 1 (11240)...
child process 0 (24312) done
Run child process 2 (24312)...
child process 1 (11240) done
Run child process 3 (11240)...
child process 3 (11240) done
child process 2 (24312) done
All subprocesses done.

外部子进程

# 子进程-外部进程

import subprocess

r = subprocess.call(["python", "-V"])  
print("Exit code:", r)  

输出

Python 3.10.8
Exit code: 0

进程间通信-队列

# 进程间通信
# multiprocessing模块提供了Queue、Pipes等多种方式来交换数据来实现进程间的通信。
from multiprocessing import Queue, Process
import time, os


# 写数据进程
def write(q):
    print(f"Process to write:{os.getpid()}")
    for value in ["A", "B", "C"]:
        print(f"Put {value} to queue ")
        q.put(value)
        time.sleep(1)


def read(q):
    print(f"Process to read: {os.getpid()}")
    while True:
        value = q.get(True)
        print(f"Get {value} from queue")


if __name__ == "__main__":
    q = Queue()  # 父进程创建Queue,并传给各个子进程
    pw = Process(target=write, args=(q,))
    pr = Process(target=read, args=(q,))
    pw.start()  # 启动写入子进程pw
    pr.start()  # 启动读取子进程pr
    pw.join()  # 等待pw结束
    pr.terminate()  # pr进程里是死循环,无法等待其结束,只能强行终止

输出

Process to write:10704
Put A to queue 
Process to read: 21360
Get A from queue
Put B to queue 
Get B from queue
Put C to queue 
Get C from queue

 多线程

一个进程还可以拥有多个执行线程,简单的说就是拥有多个可以获得 CPU 调度的执行单元,这就是所谓的线程。由于线程在同一个进程下,它们可以共享相同的上下文,因此相对于进程而言,线程间的信息共享和通信更加容易。当然在单核 CPU 系统中,多个线程不可能同时执行,因为在某个时刻只有一个线程能够获得 CPU。多个线程通过共享 CPU 执行时间的方式来达到并发的效果。

线程指的是进程中一个单一顺序的控制流,是应用程序中工作的最小单元。线程是属于进程的,线程运行在进程空间内,同一进程所产生的线程共享同一内存空间,当进程退出时该进程所产生的线程都会被强制退出并清除。线程可与属于同一进程的其它线程共享进程所拥有的全部资源,但是其本身基本上不拥有系统资源,只拥有一点在运行中必不可少的信息(如程序计数器、一组寄存器和栈)。一个进程中可以并发多个线程,每条线程并行执行不同的任务。由于同一进程中的多个线程共享数据,在程序执行中线程切换可能出现数据不一致的情况,因此引入了线程锁。 

 实例化 Thread 类创建线程

import time, threading


# 新线程执行的代码:
def run_thread():
    print(f"thread {threading.current_thread().name} is running")
    time.sleep(1)
    print(f"thread {threading.current_thread().name} end")


if __name__ == "__main__":
    print(f"thread {threading.current_thread().name} is running")
    t = threading.Thread(target=run_thread, name="LoopThread")  # 使用 Thread 类创建线程对象
    t.start()  # 激活线程
    t.join()  # 等待线程结束
    print(f"thread {threading.current_thread().name} end")

 输出

thread MainThread is running
thread LoopThread is running
thread LoopThread end
thread MainThread end

 自定义线程类

import time, threading


class TestThread(threading.Thread):
    name = "测试线程"

    def __init__(self, params):
        self.params = params
        super().__init__()

    def run(self):
        print(f"thread {threading.current_thread().name} is running")
        print(f"params:{self.params}")
        time.sleep(1)
        print(f"thread {threading.current_thread().name} end")


if __name__ == "__main__":
    print(f"thread {threading.current_thread().name} is running")
    t = TestThread(1)
    t.start()  # 激活线程
    t.join()  # 等待线程结束
    print(f"thread {threading.current_thread().name} end")

输出

thread MainThread is running
thread 测试线程 is running
params:1
thread 测试线程 end
thread MainThread end

线程池

import time, threading
from concurrent.futures import ThreadPoolExecutor


# 新线程执行的代码:
def run_thread(params):
    print(f"thread {threading.current_thread().name} is running")
    print(f"params:{params}")
    time.sleep(1)
    print(f"thread {threading.current_thread().name} end")


if __name__ == "__main__":
    with ThreadPoolExecutor(max_workers=2) as pool:
        for i in range(3):
            pool.submit(run_thread, i)

输出

thread ThreadPoolExecutor-0_0 is running
params:0
thread ThreadPoolExecutor-0_1 is running
params:1
thread ThreadPoolExecutor-0_0 end
thread ThreadPoolExecutor-0_0 is running
params:2
thread ThreadPoolExecutor-0_1 end
thread ThreadPoolExecutor-0_0 end

守护线程

守护线程(Deamon Thread)是一种在后台运行的特殊线程,其生命周期依赖于其他非守护线程(即普通线程)。以下是两者的关键区别和特点:
生命周期:  守护线程随程序退出而终止。普通线程必须自行执行完毕才会让程序终止。
用途‌:守护线程用于后台支持任务(如日志、监控)。普通线程用于核心任务。
优先级‌:守护线程‌优先级通常较低(但可手动调整)。普通线程‌优先级默认正常,可自定义。
终止条件:守护线程‌在所有普通线程结束时自动终止。普通线程必须显式完成或中断。
资源释放:守护线程可能被强制终止,不保证释放资源。普通线程通常需确保资源释放。
import time, threading


def run_thread(sec):
    print(f"thread {threading.current_thread().name} is running")
    time.sleep(sec)
    print(f"thread {threading.current_thread().name} end")


if __name__ == "__main__":
    print(f"thread {threading.current_thread().name} is running")

    t1 = threading.Thread(target=run_thread, args=(1,), name="LoopThread")
    t1.start()
    t2 = threading.Thread(target=run_thread, kwargs={"sec": 3}, name="DeamonThread")
    t2.start()

    t1.join()  # 等待线程结束
    print(f"thread {threading.current_thread().name} end")

输出

thread MainThread is running
thread LoopThread is running
thread DeamonThread is running
thread LoopThread end
thread MainThread end
thread DeamonThread end

线程锁 

多线程和多进程最大的不同在于:
多进程中,同一个变量,各自有一份拷贝存在于每个进程中,互不影响,
而多线程中,所有变量都由所有线程共享,线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,CPU接着执行其他线程。所以,任何一个变量都可以被任何一个线程修改,因此引入了线程锁。
 
RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。 如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。

from threading import RLock, current_thread, Thread
from concurrent.futures import ThreadPoolExecutor
import time


class Count(object):

    def __init__(self):
        self.count = 0
        self.lock = RLock()
        super().__init__()

    def add(self):
        self.lock.acquire()  # 获得锁
        print(f"thread {current_thread().name} get lock")

        self.count += 1
        print(f"count: {self.count}")
        time.sleep(1)

        self.lock.release()  # 释放锁
        print(f"thread {current_thread().name} release lock")

    def sub(self):
        # 通过上下文语法获得锁和释放锁
        with self.lock:
            self.count -= 1
            print(f"count: {self.count}")
            time.sleep(1)


if __name__ == "__main__":
    print(f"thread {current_thread().name} is running")

    c = Count()
    with ThreadPoolExecutor(max_workers=2) as pool:
        for i in range(4):
            pool.submit(c.add)

    with ThreadPoolExecutor(max_workers=2) as pool:
        for i in range(4):
            pool.submit(c.sub)

    print(f"thread {current_thread().name} end")

输出

thread ThreadPoolExecutor-0_0 get lock
count: 1
thread ThreadPoolExecutor-0_0 release lock
thread ThreadPoolExecutor-0_1 get lock
count: 2
thread ThreadPoolExecutor-0_1 release lock
thread ThreadPoolExecutor-0_0 get lock
count: 3
thread ThreadPoolExecutor-0_0 release lock
count: 2
count: 1
count: 0
thread MainThread end

线程事件 

python线程的事件用于主线程控制其他线程的执行,事件主要提供了三个方法 set、wait、clear。
事件处理的机制:全局定义了一个'Flag',如果'Flag'值为 False,那么当程序执行 event.wait 方法时就会阻塞,如果'Flag'值为True,那么event.wait 方法时便不再阻塞。
clear:将'Flag'设置为False
set:将'Flag'设置为True
Event.isSet() :判断标识位是否为True。
import threading


def func(event):
    print(f"{threading.current_thread().name} start")
    event.wait()
    print(f"{threading.current_thread().name} execute")


if __name__ == "__main__":
    evt = threading.Event()
    evt.clear()

    for i in range(3):
        t = threading.Thread(target=func, args=(evt,))
        t.start()

    input_str = ""
    while True:
        input_str = input("input:") + "\n"
        if input_str.strip() == "true":
            evt.set()
            break

输出

Thread-1 (func) start
Thread-2 (func) start
Thread-3 (func) start
input:true
Thread-3 (func) execute
Thread-2 (func) execute
Thread-1 (func) execute

Condition

Python线程模块中的threading.Condition类是一种用于多线程间协作的高级同步原语,其主要作用包括以下核心功能:
1. ‌线程间通信与协调‌
    使用‌等待-通知机制‌:允许线程在条件不满足时主动挂起(wait()),并在其他线程修改条件后通过notify()或notify_all()唤醒等待的线程。这种机制避免了线程“忙等待”导致的资源浪费。
    解决‌复杂同步问题‌:例如生产者-消费者模型、任务调度等场景。生产者生产数据后通知消费者线程消费,消费者线程在消费后通知生产者继续生产。
2. ‌共享资源的线程安全访问‌
   与锁结合使用‌:Condition内部绑定了一个锁(默认为RLock),通过acquire()和release()保证对共享资源的互斥访问。
   条件依赖的检查‌:线程在修改共享资源后,通过notify()通知其他线程资源状态已变化,确保线程仅在条件满足时操作资源
import threading, time


def read(cond):
    with cond:
        print(f"{threading.current_thread().name} wait")
        cond.wait()
        print(f"{threading.current_thread().name} wake")


def write(cond):
    with cond:
        cond.notify_all()
        print(f"{threading.current_thread().name} notify all")


if __name__ == "__main__":
    condition = threading.Condition()
    for i in range(2):
        tr = threading.Thread(target=read, args=(condition,))
        tr.start()
        time.sleep(1)

    tw = threading.Thread(target=write, args=(condition,))
    tw.start()

 输出

Thread-1 (read) wait
Thread-2 (read) wait
Thread-3 (write) notify all
Thread-2 (read) wake
Thread-1 (read) wake

队列Queue

队列,线程安全
q = queue.Queue(maxsize=0)   构造一个先进先出队列,maxsize指定队列长度,为0 时,表示队列长度无限制。
q.join()    等到队列为空的时候,再执行别的操作
q.qsize()   返回队列的大小 (不可靠)
q.empty()   当队列为空的时候,返回True 否则返回False (不可靠)
q.full()    当队列满的时候,返回True,否则返回False (不可靠)
q.put(item, block=True, timeout=None)   将item放入Queue尾部
      可选参数block默认为True,表示当队列满时,会等待队列给出可用位置,block为False时为非阻塞,此时如果队列已满,会引发queue.Full 异常。
      可选参数timeout,表示设置阻塞时间,超时后如果队列无法给出放入item的位置,则引发 queue.Full 异常
q.get(block=True, timeout=None)   移除并返回队列头部的一个值
      可选参数block默认为True,表示获取值的时候,如果队列为空,则阻塞,为False时,不阻塞,若此时队列为空,则引发 queue.Empty异常。
      可选参数timeout,表示设置阻塞时间,超时后如果队列为空,则引发Empty异常。
q.put_nowait(item)   等效于 put(item,block=False)
q.get_nowait()  等效于 get(item,block=False)
import queue


if __name__ == "__main__":
    q = queue.Queue(3)

    for i in range(3):
        q.put(i)
        print(f"put {i}")

    while not q.empty():
        msg = q.get(timeout=5)  # 超时机制避免死锁
        print(f"get {msg}")

        q.task_done()  # 标记任务完成

    q.join()  # 阻塞直到所有任务完成
    print("done")

输出

put 0
put 1
put 2
get 0
get 1
get 2
done

和线程一起使用

import queue, threading


def producer(q, num, c_num):
    print(f"{threading.current_thread().name} is running", flush=True)
    for i in range(num):
        q.put(i)
        print(f"{threading.current_thread().name} put {i}", flush=True)

    for i in range(c_num):
        q.put("done")
        print(f"{threading.current_thread().name} put done", flush=True)

    print(f"{threading.current_thread().name} end", flush=True)


def consumer(q):
    print(f"{threading.current_thread().name} is running", flush=True)
    while True:
        msg = q.get(timeout=5)
        print(f"{threading.current_thread().name} get {msg}", flush=True)
        q.task_done()
        if msg == "done":
            break
    print(f"{threading.current_thread().name} end", flush=True)


if __name__ == "__main__":
    num = 5
    tc_len = 3
    q_len = 3
    q = queue.Queue(q_len)

    tc = []
    for i in range(tc_len):
        t = threading.Thread(target=consumer, args=(q,))
        t.start()
        tc.append(t)

    tp = threading.Thread(target=producer, args=(q, num, tc_len))
    tp.start()

    for t in tc:
        t.join()

    q.join()
    print("done", flush=True)

输出

Thread-1 (consumer) is running
Thread-2 (consumer) is running
Thread-3 (consumer) is running
Thread-4 (producer) is running
Thread-4 (producer) put 0     
Thread-1 (consumer) get 0     
Thread-4 (producer) put 1     
Thread-2 (consumer) get 1     
Thread-3 (consumer) get 2     
Thread-4 (producer) put 2     
Thread-4 (producer) put 3     
Thread-1 (consumer) get 3     
Thread-2 (consumer) get 4
Thread-4 (producer) put 4
Thread-4 (producer) put done
Thread-3 (consumer) get done
Thread-4 (producer) put done
Thread-3 (consumer) end
Thread-4 (producer) put done
Thread-1 (consumer) get done
Thread-4 (producer) end
Thread-1 (consumer) end
Thread-2 (consumer) get done
Thread-2 (consumer) end
done

local

 在 Python 中,threading.local() 用于创建‌线程本地存储(Thread-Local Data)‌,它的核心作用是‌为每个线程提供独立的数据副本‌,从而在多线程环境中实现数据隔离,避免线程间共享变量导致的竞争问题。
实现原理‌:threading.local() 实例内部维护一个字典,以线程 ID 为键,存储各自的数据。每个线程访问 local 对象的属性时,实际是在操作自己线程的独立数据副本。
在多线程环境下,一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。
一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。
ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。
import threading


def func2():
    print(threading.current_thread().name, local_data.params)


def func(num):
    local_data.params = num
    func2()


if __name__ == "__main__":
    local_data = threading.local()
    t1 = threading.Thread(target=func, args=(2,))
    t2 = threading.Thread(target=func, args=(3,))
    t1.start()
    t2.start()
    t1.join()
    t2.join()

    if hasattr(local_data, "params"):
        print(local_data.params)
    else:
        print(f"{threading.current_thread().name} has no params attr")

输出

Thread-1 (func) 2
Thread-2 (func) 3
MainThread has no params attr

协程

协程 Coroutine,又称微线程,用户线程。Python 对协程的支持是通过 generator 实现的。

协程存在的意义:对于多线程应用,CPU 通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。而协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。
协程的适用场景:当程序中存在大量不需要 CPU 的操作(IO)时,适用于协程。

子程序或者称为函数在所有语言中都是通过栈实现层级调用。子程序调用总是一个入口,一次返回,调用顺序是明确的。而协程的调用和子程序不同。
协程看上去也是子程序,但执行过程中在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。

协程在一个线程执行,和多线程比,协程的优势在于:
1 协程极高的执行效率。协程切换由程序自身控制,因此没有线程切换的开销。和多线程比,数量越多,协程的性能优势就越明显。
2 不需要多线程的锁机制。协程控制共享资源不加锁,只需要判断状态,所以执行效率比多线程高很多。

协程利用多核 CPU 的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
def consumer():
    r = ""
    while True:
        n = yield r
        if not n:
            return
        print(f"[CONSUMER] Consuming {n} ")
        r = "200 OK"


def producer(c):
    c.send(None)
    n = 0
    while n < 3:
        n = n + 1
        print(f"[PRODUCER] Producing {n}")
        r = c.send(n)
        print(f"[PRODUCER] Consumer return: {r}")

    c.close()


c = consumer()
producer(c)
输出
[PRODUCER] Producing 1
[CONSUMER] Consuming 1
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 2
[CONSUMER] Consuming 2
[PRODUCER] Consumer return: 200 OK
[PRODUCER] Producing 3
[CONSUMER] Consuming 3
[PRODUCER] Consumer return: 200 OK

异步

异步编程是一种编写能够在单线程中同时处理多个任务的编程方式。
与传统的同步编程相比,异步编程的主要优势在于能够提高程序的并发性和响应性,尤其适用于IO密集型任务,如网络通信、数据库访问等。
异步编程的核心思想是避免阻塞,即当一个任务在等待某个操作完成时,可以让出CPU执行权,让其他任务继续执行。这样可以充分利用CPU的时间片,提高程序的整体效率。

事件循环
事件循环是异步编程的核心机制,它负责协调和调度协程的执行,以及处理IO操作和定时器等事件。
它会循环监听事件的发生,并根据事件的类型选择适当的协程进行调度。

asyncio是Python 3.4版本引入的标准库,用于实现异步编程。它基于事件循环(Event Loop)模型,通过协程(Coroutines)来实现任务的切换和调度。
在asyncio中可以使用async关键字定义协程函数,并使用await关键字挂起协程的执行,等待耗时操作完成后再恢复执行。
由async修饰的普通函数变成了异步函数,异步函数不同于普通函数不可能被直接调用
await的作用是挂起自身的协程,直到await修饰的协程完成并返回结果
await只能使用在有async修饰的函数中不然会报错
 
协程(Coroutines):
协程是asyncio中的基本单元,使用async def定义。
使用await调用另一个协程,表示暂停当前协程,等待被调用的协程完成后再继续。
它们是异步执行的,不会阻塞整个程序。
import asyncio


async def long_time_task():
    print("long time task begin")
    await asyncio.sleep(2)  # 休眠几秒
    print("long time task end")


if __name__ == "__main__":
    print("main begin")
    asyncio.run(long_time_task())  # 创建事件循环,运行一个协程,关闭事件循环。
    print("main end")
输出
main begin
long time task begin
long time task end
main end

await

可等待对象
如果一个对象可以在 await 语句中使用,那么它就是可等待对象。可等待对象有三种主要类型: 协程, 任务 和 Future.
# 可等待对象
# 如果一个对象可以在 await 语句中使用,那么它就是可等待对象。可等待对象有三种主要类型: 协程, 任务 和 Future.

import asyncio, time


async def long_time_task(id):
    print(f"long time task {id} begin {time.time()}")
    await asyncio.sleep(id)  # 休眠几秒
    print(f"long time task {id} end {time.time()}")
    return "data"


async def main():
    res1 = await long_time_task(1)
    print(f"result1: {res1}")

    res2 = await long_time_task(2)
    print(f"result2: {res2}")


if __name__ == "__main__":
    print(f"main begin {time.time()}")
    asyncio.run(main())  # 创建事件循环,运行一个协程,关闭事件循环。
    print(f"main end {time.time()}")

输出,看着和同步执行没什么分别 

main begin 1747896057.2969735
long time task 1 begin 1747896057.300656
long time task 1 end 1747896058.312246
result1: data
long time task 2 begin 1747896058.312246
long time task 2 end 1747896060.3131278
result2: data
main end 1747896060.313629

Task

任务用于并发调度协程。
import asyncio, time


async def long_time_task(id):
    print(f"long time task{id} begin {time.time()}")
    await asyncio.sleep(id)  # 休眠几秒
    print(f"long time task{id} end {time.time()}")
    return "data" + str(id)


async def main():
    task1 = asyncio.create_task(long_time_task(1))  # 创建一个并发任务
    task2 = asyncio.create_task(long_time_task(2))
    print("create tasks")

    res2 = await task2
    print(f"task2 result:{res2}")
    res1 = await task1
    print(f"task1 result:{res1}")


if __name__ == "__main__":
    print(f"main begin {time.time()}")
    asyncio.run(main())  # 创建事件循环,运行一个协程,关闭事件循环。
    print(f"main end {time.time()}")

输出,任务1和2 并发执行,整体执行时间比2个任务中较长执行时间多一点

 

main begin 1747896235.9833534
create tasks
long time task1 begin 1747896235.9875793
long time task2 begin 1747896235.9875793
long time task1 end 1747896236.9962473
long time task2 end 1747896238.004733
task2 result:data2        
task1 result:data1        
main end 1747896238.005734

并发执行多个任务

# 并发执行多个任务

import asyncio, time


async def long_time_task(id):
    await asyncio.sleep(id)  # 休眠几秒
    return "data" + str(id)


async def main():
    task1 = asyncio.create_task(long_time_task(1))  # 创建一个并发任务
    task2 = asyncio.create_task(long_time_task(2))

    print(f"task begin {time.time()}")
    results = await asyncio.gather(task1, task2)  # 并发执行多个任务,等待任务全部完成
    print(f"task end {time.time()}")
    print(f"results: {results}")

    print(f"coroutine begin {time.time()}")
    results = await asyncio.gather(long_time_task(1), long_time_task(2))  # 并发执行多个任务,等待任务全部完成
    print(f"coroutine end {time.time()}")
    print(f"results: {results}")


if __name__ == "__main__":
    asyncio.run(main())  # 创建事件循环,运行一个协程,关闭事件循环。

输出

task begin 1747896555.7186544
task end 1747896557.7444022
results: ['data1', 'data2']       
coroutine begin 1747896557.7444022
coroutine end 1747896559.7539423
results: ['data1', 'data2']

超时控制

import asyncio


async def long_time_task(id):
    await asyncio.sleep(id + 1)  # 休眠几秒
    return "data" + str(id)


async def main():
    try:
        result = await asyncio.wait_for(long_time_task(3), timeout=1)  # 超时控制
        print(f"result: {result}")

    except asyncio.TimeoutError:
        print("time is out")


if __name__ == "__main__":
    asyncio.run(main())

输出

time is out 

事件Event

asyncio 事件可被用来通知多个 asyncio 任务已经有事件发生。
Event 对象会管理一个内部flag,可通过 set() 方法将其设为 true 并通过 clear() 方法将其重设为 false。 wait() 方法会阻塞直至该flag被设为 true。 该flag初始时会被设为 false。 
import asyncio


async def wait_task(event):
    print("等待事件触发")
    await event.wait()  # 阻塞直至flag被设为 true
    print("事件被触发了")


async def main():
    print("main begin")

    event = asyncio.Event()
    task = asyncio.create_task(wait_task(event))
    print("create task")

    await asyncio.sleep(2)
    print("开始触发事件")
    event.set()  # 将flag设为 true

    await task  # 将此语句放在上面的await语句前将会一直阻塞

    print("main end")


if __name__ == "__main__":
    asyncio.run(main())

 输出

main begin 
create task
等待事件触发
开始触发事件
事件被触发了
main end 

 锁Lock

asyncio 锁可被用来保证对共享资源的独占访问。
 
import asyncio


async def task(lock, params):
    print(f"{params} waiting for lock")
    async with lock:
        print(f"{params} got lock")
        await asyncio.sleep(2)
    print(f"{params} release lock")


async def main():
    lock = asyncio.Lock()
    await asyncio.gather(task(lock, "a"), task(lock, "b"))


if __name__ == "__main__":
    asyncio.run(main())
输出
a waiting for lock
a got lock
b waiting for lock
a release lock
b got lock
b release lock

队列Queue

import asyncio


async def consumer(name, queue):
    while True:
        item = await queue.get()
        print(f"{name} 收到 {item}")
        queue.task_done()


async def producer(queue, num_items):
    for item in range(num_items):
        await queue.put(item)
        print(f"生产了 {item}")
        await asyncio.sleep(1)


async def main():
    queue = asyncio.Queue(maxsize=3)
    await asyncio.gather(producer(queue, num_items=5), consumer("消费者A", queue), consumer("消费者B", queue))


if __name__ == "__main__":
    asyncio.run(main())
 输出
生产了 0
消费者A 收到 0
生产了 1
消费者A 收到 1
生产了 2
消费者B 收到 2
生产了 3
消费者A 收到 3
生产了 4
消费者B 收到 4

 信号量Semaphore

信号量控制并发数
import asyncio


async def limited_task(semaphore, name):
    async with semaphore:
        print(f"Task {name} started")
        await asyncio.sleep(1)
        print(f"Task {name} completed")


async def main():
    semaphore = asyncio.Semaphore(2)  # 信号量
    await asyncio.gather(limited_task(semaphore, "A"), limited_task(semaphore, "B"), limited_task(semaphore, "C"), limited_task(semaphore, "D"))


if __name__ == "__main__":
    asyncio.run(main())
输出
Task A started
Task B started
Task A completed
Task B completed
Task C started  
Task D started  
Task C completed
Task D completed

 异步网络请求

 使用aiohttp库实现异步网络请求。
pip install aiohttp
import aiohttp
import asyncio


async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            return await response.text()


async def main():
    url = "https://www.example.com"
    html = await fetch(url)
    print(html)


if __name__ == "__main__":
    asyncio.run(main())

 异步文件读写

import aiofiles
import asyncio


async def write_file():
    async with aiofiles.open("example.txt", mode="w") as f:
        await f.write("Hello, asyncio!")


async def read_file():
    async with aiofiles.open("example.txt", mode="r") as f:
        content = await f.read()
        print(content)


async def main():
    await write_file()
    await read_file()


if __name__ == "__main__":
    asyncio.run(main())
输出
Hello, asyncio!

在线程或者进程池中执行阻塞任务

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


def long_time_task():
    time.sleep(2)
    return "data"


def compute_task():
    time.sleep(1)
    return 3


async def main():
    loop = asyncio.get_running_loop()  # 获取当前运行的事件循环
    result = await loop.run_in_executor(None, long_time_task)  # 在当前线程中运行阻塞函数。
    print(f"loop result: {result}")

    with ThreadPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, long_time_task)  # 在自定义线程中运行阻塞函数。
        print(f"io result: {result}")

    with ProcessPoolExecutor() as pool:
        result = await loop.run_in_executor(pool, compute_task)  # 在自定义进程中运行阻塞函数。
        print(f"cpu result: {result}")


if __name__ == "__main__":
    asyncio.run(main())
输出
 
loop result: data
io result: data
cpu result: 3

 计算密集型和 IO 密集型

计算密集型任务的特点是要进行大量的计算,消耗 CPU 资源,比如计算圆周率、对视频进行高清解码等等,全靠 CPU 的运算能力。这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU 执行任务的效率就越低,所以,要最高效地利用 CPU,计算密集型任务同时进行的数量应当等于 CPU 的核心数。
计算密集型任务由于主要消耗 CPU 资源,因此,代码运行效率至关重要。Python 这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用 C 语言编写。

涉及到网络、磁盘 IO 的任务都是 IO 密集型任务,这类任务的特点是 CPU 消耗很少,任务的大部分时间都在等待 IO 操作完成(因为 IO 的速度远远低于 CPU 和内存的速度)。常见的大部分任务都是 IO 密集型任务,比如 Web 应用。
IO 密集型任务执行期间,99%的时间都花在 IO 上,花在 CPU 上的时间很少,因此,用运行速度极快的 C 语言替换用 Python 这样运行速度极低的脚本语言,完全无法提升运行效率。对于 IO 密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C 语言最差。

并发(concurrency)和并行(parallel)

并发通常是指同一时刻只能有一条指令执行,多个线程对应的指令被快速轮换地执行。比如一个处理器,它先执行线程 A 的指令一段时间,再执行线程 B 的指令一段时间,再切回到线程 A 执行一段时间。由于处理器执行指令的速度和切换的速度极快,人们完全感知不到计算机在这个过程中有多个线程切换上下文执行的操作,这就使得宏观上看起来多个线程在同时运行,但微观上其实只有一个线程在执行。

并行是指同一时刻,有多条指令在多个处理器上同时执行,并行必须要依赖于多个处理器,不论是从宏观上还是微观上,多个线程可以在同一时刻一起执行的。
 
posted @ 2024-01-17 13:47  carol2014  阅读(114)  评论(0)    收藏  举报