0
点赞
收藏
分享

微信扫一扫

Python并发编程

公众号:黑客编程狮,专做免费编程知识分享!

Python并发编程_信号量

预计更新

  1. Python 简介
  • Python 简介和历史
  • Python 特点和优势
  • 安装 Python
  1. 变量和数据类型
  • 变量和标识符
  • 基本数据类型:数字、字符串、布尔值等
  • 字符串操作
  • 列表、元组和字典
  1. 控制语句和函数
  • 分支结构:if/else 语句
  • 循环结构:for 和 while 循环
  • 函数
  • 参数传递与返回值
  • Lambda 表达式
  1. 模块和文件 IO
  • 模块的概念
  • 导入模块
  • 文件 IO
  • 序列化和反序列化
  1. 异常处理
  • 异常简介
  • try/except 语句
  • 自定义异常
  1. 面向对象编程
  • 类和对象
  • 继承和多态
  • 属性和方法
  • 抽象类和接口
  1. 正则表达式
  • 正则表达式概述
  • 匹配和搜索
  • 替换和分割
  1. 并发编程
  • 多线程
  • 多进程
  • 协程和异步编程
  1. 数据库编程
  • 关系型数据库介绍
  • 使用 SQLite 数据库
  • 使用 MySQL 数据库
  • 使用 PostgreSQL 数据库
  1. 网络编程
  • Socket 编程简介
  • TCP Socket 编程
  • UDP Socket 编程
  • HTTP 编程
  1. Web 开发框架 Flask
  • Flask 简介
  • 安装 Flask
  • 路由和视图函数
  • 模板和静态文件
  1. 数据分析和科学计算
  • NumPy 基础
  • Pandas 基础
  • Matplotlib 基础
  1. 机器学习入门
  • 机器学习概述
  • 监督学习和非监督学习
  • Scikit-Learn 简介
  • 利用 Scikit-Learn 进行数据预处理和模型训练
  1. 自然语言处理
  • 自然语言处理概述
  • 中文分词和处理
  • 文本分类和情感分析
  1. 游戏开发与 Pygame
  • Pygame 简介
  • Pygame 基础
  • 开发一个简单的游戏
  1. 并发编程
  • 多线程
  • 多进程
  • 协程和异步编程

多线程
Python多线程是指使用Python编写并发程序时,通过创建多个线程来提高程序的执行效率。多线程可以让程序在同一时间内同时处理多个任务,从而提高程序的运行速度和响应能力。在Python中,多线程的实现主要依赖于threading模块。

  1. 线程和进程的区别

在开始讨论Python多线程之前,需要先了解线程和进程的概念以及它们之间的区别。

进程是操作系统资源分配的基本单位,每个进程都有自己独立的地址空间,并占用着一定的系统资源(如CPU、内存等)。进程与进程之间是相互独立的,一个进程崩溃或者被杀死不会影响到其他进程。

线程是进程中的执行单元,一个进程可以包含多个线程。线程之间共享进程资源,每个线程有自己的栈和局部变量,但是它们共享全局变量、静态变量等。不同线程之间切换的开销比进程之间切换的开销小得多。

  1. Python threading模块

Python提供了threading模块来支持多线程编程。该模块提供了Thread类来创建线程,常用的方法有:

Thread(target=, args=):创建新的线程。

start():启动线程。

join([time]):等待线程运行结束。

is_alive():判断线程是否在运行。

  1. 创建线程

创建一个新的线程需要使用Thread类,它的构造函数如下:

Thread(target=, args=(), name=)

其中,target参数为该线程所要执行的目标函数。如果不指定name,则每个线程会自动生成一个唯一的名字。

示例代码如下:

import threading
import time

# 定义线程处理函数
def thread_func(thread_id):
    print('Thread %d is running...' % thread_id)
    time.sleep(2) # 模拟线程执行时间
    print('Thread %d is done.' % thread_id)

# 创建5个线程并启动
for i in range(5):
    t = threading.Thread(target=thread_func, args=(i,))
    t.start()

# 等待所有线程运行结束
for t in threading.enumerate():
    if t != threading.current_thread():
        t.join()

上面的代码中,我们首先定义了一个线程处理函数thread_func,它接受一个参数thread_id,用于标识当前线程。然后我们使用threading.Thread类创建了5个线程,并分别传递给它们不同的thread_id参数。

通过调用start()方法来启动线程,这将会调用线程处理函数。主线程继续往下执行,而新产生的线程在后台运行。

最后,我们使用enumerate()方法获取所有的线程,并调用join()方法等待它们运行结束。

  1. 线程同步

在多线程编程中,线程之间会共享一些数据,如果多个线程同时修改同一个变量可能会导致不可预期的结果。所以需要对共享资源进行同步处理。

Python提供了Lock、RLock、Semaphore、Event、Condition等同步机制来实现线程同步。其中,Lock和RLock都是互斥锁,只允许一个线程访问被保护的共享资源;Semaphore是信号量,允许多个线程同时访问某个资源;Event可以用于线程之间通信,一个线程可以通过set()方法发出事件,其他线程可以通过wait()方法等待该事件的发生;Condition可以用于控制线程执行的顺序,它可以让某些线程等待特定条件的发生,再继续执行。

下面我们分别介绍这些同步机制的使用方法。

4.1 Lock

Lock是最简单也是最常用的同步机制,它的作用是保证对共享资源的互斥访问。

在Python中,Lock可以通过threading模块来创建:

lock = threading.Lock()

然后在需要保护的代码块前后加上acquire()和release()方法,如下所示:

import threading

# 共享变量
count = 0

# 创建锁
lock = threading.Lock()

# 线程处理函数
def thread_func():
    global count, lock
    with lock:
        for i in range(100000):
            count += 1

# 创建10个线程并启动
threads = []
for i in range(10):
    t = threading.Thread(target=thread_func)
    threads.append(t)
    t.start()

# 等待所有线程运行结束
for t in threads:
    t.join()

# 输出count的值
print('count=%d' % count)

上面的代码中,我们先定义了一个全局变量count,然后使用threading.Lock()创建了一个锁对象lock。在线程处理函数中,我们通过with lock:语句获取锁,然后对count进行累加操作。

由于多个线程会同时竞争同一个锁,所以只有一个线程能够获得锁,并执行累加操作。其他线程则会阻塞在with lock:语句处,等待锁的释放。

最后,我们通过输出count的值检查程序的正确性。

4.2 RLock

RLock是可重入锁,它允许同一个线程多次获取锁。这对于一些需要递归调用的场景非常有用。其使用方法与Lock类似,唯一的区别就是可以多次acquire()。

import threading

# 共享变量
count = 0

# 创建锁
lock = threading.RLock()

# 线程处理函数
def thread_func():
    global count, lock
    with lock:
        with lock: # 多次acquire()
            for i in range(100000):
                count += 1

# 创建10个线程并启动
threads = []
for i in range(10):
    t = threading.Thread(target=thread_func)
    threads.append(t)
    t.start()

# 等待所有线程运行结束
for t in threads:
    t.join()

# 输出count的值
print('count=%d' % count)

上面的代码中,我们创建了一个RLock对象lock,然后在线程处理函数中使用多次with lock:语句来演示RLock的使用方法。

4.3 Semaphore

Semaphore是信号量,它控制对共享资源的访问数量。当一个线程获得了信号量之后,其他线程必须等待该线程释放信号量后才能继续执行。

在Python中,Semaphore可以通过threading模块来创建:

semaphore = threading.Semaphore(value)

其中value参数表示信号量的初始值,默认为1。然后我们可以使用acquire()和release()方法来获取和释放信号量。

import threading

# 共享变量
count = 0

# 创建信号量
semaphore = threading.Semaphore(value=5)

# 线程处理函数
def thread_func():
    global count, semaphore
    with semaphore: # 获取信号量
        for i in range(100000):
            count += 1
    semaphore.release() # 释放信号量

# 创建10个线程并启动
threads = []
for i in range(10):
    t = threading.Thread(target=thread_func)
    threads.append(t)
    t.start()

# 等待所有线程运行结束
for t in threads:
    t.join()

# 输出count的值print('count=%d' % count)

上面的代码中,我们先创建了一个Semaphore对象`semaphore`,将其初始值设为5。然后在线程处理函数中使用`with semaphore:`语句来获取信号量,执行累加操作后再释放信号量。

由于Semaphore的初始值为5,所以最多只有5个线程可以同时执行累加操作,其他线程必须等待信号量的释放才能继续执行。

4.4 Event

Event是用于线程之间通信的同步机制,它允许一个线程发出事件,其他线程等待该事件的发生。在Python中,Event可以通过threading模块来创建:

```python
evt = threading.Event()

然后我们可以使用set()方法发出事件,使用wait()方法等待事件的发生。

import threading

# 创建事件
evt = threading.Event()

# 线程1处理函数
def thread_func1():
    print('Thread 1 is waiting...')
    evt.wait() # 等待事件发生
    print('Thread 1 is done.')

# 线程2处理函数
def thread_func2():
    print('Thread 2 is running...')
    for i in range(5):
        print('Thread 2 is working...')
        time.sleep(1)
    evt.set() # 发出事件
    print('Thread 2 is done.')

# 创建线程并启动
t1 = threading.Thread(target=thread_func1)
t2 = threading.Thread(target=thread_func2)
t1.start()
t2.start()

# 等待所有线程运行结束
t1.join()
t2.join()

上面的代码中,我们创建了一个Event对象evt。在线程1处理函数中,我们使用evt.wait()语句等待事件发生;在线程2处理函数中,我们使用evt.set()方法发出事件。

由于线程1等待事件的发生,所以会一直阻塞在evt.wait()语句处,直到线程2发出事件才能继续执行。

4.5 Condition

Condition是控制线程执行顺序的同步机制,它可以让某些线程等待特定条件的发生,再继续执行。

在Python中,Condition可以通过threading模块来创建:

cond = threading.Condition(lock=None)

其中lock参数表示该Condition使用的锁,默认为RLock。

然后我们可以使用wait()、notify()和notify_all()方法来控制线程之间的执行顺序。

import threading

# 共享变量
count = 0

# 创建条件变量
cond = threading.Condition()

# 线程1处理函数
def thread_func1():
    global count, cond
    with cond:
        while count < 5:
            print('Thread 1 is waiting...')
            cond.wait() # 等待条件变量
        print('Thread 1 is done.')

# 线程2处理函数
def thread_func2():
    global count, cond
    with cond:
        for i in range(5):
            count += 1
            print('Thread 2 is working...')
            time.sleep(1)
        cond.notify() # 通知条件变量已经满足
    print('Thread 2 is done.')

# 创建线程并启动
t1 = threading.Thread(target=thread_func1)
t2 = threading.Thread(target=thread_func2)
t1.start()
t2.start()

# 等待所有线程运行结束
t1.join()
t2.join()

上面的代码中,我们创建了一个Condition对象cond。在线程1处理函数中,我们使用while count < 5:语句等待条件变量;在线程2处理函数中,我们通过累加操作使得条件变量满足,并使用cond.notify()方法通知等待的线程。

由于线程1等待条件变量的发生,所以会一直阻塞在cond.wait()语句处,直到线程2发出通知才能继续执行。

  1. 线程安全问题

多线程编程中最常见的问题就是线程安全问题。由于多个线程可能同时访问共享资源,所以如果不加以保护,就会出现数据不一致等问题。

在Python中,我们可以使用锁、信号量、条件变量等同步机制来解决线程安全问题。具体来说,我们可以按照以下步骤来实现线程安全:

  1. 定义共享资源并初始化。
  2. 创建互斥锁或信号量等同步机制。
  3. 在对共享资源进行操作前获取锁或信号量。
  4. 对共享资源进行操作。
  5. 释放锁或信号量。

例如,在第4节中,我们使用锁和信号量等同步机制来保护了count这个共享变量,避免了多个线程同时修改它而引发的线程安全问题。

另外,为了避免死锁问题,我们还需要注意同步机制的使用方法。一般来说,当多个线程需要获取多个锁时,我们应该按照固定的顺序获取锁,避免死锁的发生。

  1. 总结

本文介绍了Python中的多线程编程,并详细讲解了线程的创建、启动、结束等基本操作,以及同步机制的使用方法。同时,我们还介绍了线程安全问题的解决方法,包括锁、信号量、条件变量等同步机制的使用。

最后,需要注意的是,在进行多线程编程时,我们应该尽可能地避免共享资源的使用,尽量使用局部变量等本地数据,从而减少线程之间的竞争和冲突。

多进程
一、概述

多进程是指在操作系统中,同时运行多个进程,每个进程都有自己的独立地址空间,它们之间相互独立,互不影响。多进程编程可以充分利用多核CPU的性能优势,提高程序的执行效率。

Python标准库中提供了multiprocessing模块,用于支持多进程编程。该模块提供了Process、Queue、Pipe等类和函数,方便我们创建和管理子进程,以及进行进程间通信。

本文将从以下几个方面介绍Python多进程编程:

  1. 进程的基本概念
  2. 创建和启动进程
  3. 进程间通信
  4. 进程池
  5. 多进程编程实践

二、进程的基本概念

进程是操作系统中的一个基本概念,它表示正在执行的程序。每个进程都有自己的独立地址空间,包括代码段、数据段、堆栈等。进程之间相互独立,互不干扰。

在Linux系统中,我们可以使用ps命令查看当前系统中正在运行的所有进程:

$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.2  19060  2520 ?        Ss   Mar22   0:03 /sbin/init
root         2  0.0  0.0      0     0 ?        S    Mar22   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        I<   Mar22   0:00 [rcu_gp]
root         4  0.0  0.0      0     0 ?        I<   Mar22   0:00 [rcu_par_gp]
...

其中,PID表示进程的ID,COMMAND表示进程的命令。

在Python中,我们可以使用os模块来获取当前进程的ID和父进程的ID:

import os

print('Current process ID:', os.getpid())
print('Parent process ID:', os.getppid())

输出结果如下:

Current process ID: 12345
Parent process ID: 67890

三、创建和启动进程

在Python中,我们可以通过multiprocessing模块来创建和启动子进程。multiprocessing模块提供了Process类,用于表示一个进程对象。

下面是一个简单的例子,演示如何使用Process类创建和启动子进程:

from multiprocessing import Process
import os

# 子进程执行的代码
def child_proc(name):
    print('Child process %s (%s) running...' % (name, os.getpid()))

if __name__ == '__main__':
    # 创建子进程并启动
    p = Process(target=child_proc, args=('test',))
    print('Parent process %s.' % os.getpid())
    p.start()
    p.join()
    print('Child process end.')

上面的代码中,我们首先定义了一个函数child_proc,用于表示子进程要执行的代码。然后,在主进程中,我们使用Process类创建了一个子进程对象p,并通过start()方法启动子进程。

注意到我们在创建子进程之前添加了一句if __name__ == '__main__':,这是因为在Windows系统中,由于多进程模块会将整个程序复制一份作为新的进程运行,如果没有加上这句判断语句,就会导致无限递归。

输出结果如下:

Parent process 12345.
Child process test (67890) running...
Child process end.

从输出结果可以看出,子进程已经成功创建并运行,打印出了子进程的ID和父进程的ID。

四、进程间通信

在多进程编程中,由于每个进程都有自己独立的地址空间,所以它们之间无法直接访问彼此的变量。为了实现进程间的数据共享和通信,我们需要使用进程间通信(IPC)机制。

multiprocessing模块提供了多种进程间通信方式,包括队列、管道、共享内存等。这里我们主要介绍Queue和Pipe两种方式。

4.1 Queue

Queue是一种线程安全的队列,可以用于多进程之间的通信。在Python中,我们可以使用multiprocessing.Queue类来创建一个队列对象。

下面是一个简单的例子,演示如何使用Queue进行进程间通信:

from multiprocessing import Process, Queue

# 子进程执行的代码
def child_proc(q):
    value = q.get() # 从队列中获取数据
    print('Child process received:', value)

if __name__ == '__main__':
    # 创建队列并传递给子进程
    q = Queue()
    p = Process(target=child_proc, args=(q,))
    
    # 往队列中放入数据
    q.put('Hello, world!')
    
    # 启动子进程
    p.start()
    p.join()

上面的代码中,我们首先创建了一个Queue对象q,然后通过Process类创建了一个子进程对象p。在主进程中,我们使用q.put()方法往队列中放入了一条数据。

在子进程中,我们使用q.get()方法获取队列中的数据,并打印出来。

输出结果如下:

Child process received: Hello, world!

可以看到,子进程成功地从队列中取出了数据并打印出来。

4.2 Pipe

Pipe是一种双向管道,可以用于两个进程之间的通信。在Python中,我们可以使用multiprocessing.Pipe类来创建一个管道对象。

下面是一个简单的例子,演示如何使用Pipe进行进程间通信:

from multiprocessing import Process, Pipe

# 子进程执行的代码
def child_proc(conn):
    data = conn.recv() # 接收主进程发送的数据
    print('Child process received:', data)
    
    conn.send('Hello, world!') # 向主进程发送数据

if __name__ == '__main__':
    # 创建管道并传递给子进程
    parent_conn, child_conn = Pipe()
    p = Process(target=child_proc, args=(child_conn,))
    
    # 向子进程发送数据
    parent_conn.send('Hello, child!')
    
    # 启动子进程
    p.start()
    
    # 接收子进程返回的数据
    data = parent_conn.recv()
    print('Parent process received:', data)
    
    p.join()

上面的代码中,我们首先使用multiprocessing.Pipe()函数创建了一对管道对象parent_connchild_conn,然后通过Process类创建了一个子进程对象p。在主进程中,我们使用parent_conn.send()方法向子进程发送了一条数据。

在子进程中,我们使用conn.recv()方法接收主进程发送的数据,并使用conn.send()方法向主进程发送了一条数据。

在主进程中,我们使用parent_conn.recv()方法接收子进程返回的数据,并打印出来。

输出结果如下:

Child process received: Hello, child!
Parent process received: Hello, world!

可以看到,主进程成功地向子进程发送了一条数据,并从子进程接收了一条数据。

五、进程池

在多进程编程中,为避免频繁创建和销毁进程带来的系统开销,我们可以使用进程池(Pool)来管理进程,以便重复利用已创建的进程。

multiprocessing模块提供了Pool类,用于创建一个进程池对象。我们可以通过调用该对象的apply()apply_async()map()imap()等方法来提交任务,并返回结果。

下面是一个简单的例子,演示如何使用进程池执行任务:

from multiprocessing import Pool
import time

# 计算平方数
def square(n):
    print('calculate square of %d' % n)
    time.sleep(1)
    return n * n

if __name__ == '__main__':
    # 创建进程池对象
    with Pool(processes=3) as pool:
        # 调用map方法执行任务
        results = pool.map(square, [1, 2, 3, 4, 5])
        print(results)

上面的代码中,我们首先定义了一个函数square,用于计算一个数的平方数。在主进程中,我们使用multiprocessing.Pool()函数创建了一个进程池对象pool,并指定了进程数为3。然后,我们调用pool.map()方法提交任务,并使用print()打印出返回结果。

输出结果如下:

calculate square of 1
calculate square of 2
calculate square of 3
calculate square of 4
calculate square of 5
[1, 4, 9, 16, 25]

可以看到,进程池成功地执行了我们提交的任务,并返回了预期的结果。

除了map方法外,进程池还提供了apply()、apply_async()、imap()等方法,具体用法可以参考官方文档。

六、多进程编程实践

在实际应用中,我们经常会遇到需要大量计算的任务,比如图像处理、机器学习等。使用多进程可以极大地提高计算效率,加快任务完成速度。下面是一个简单的例子,演示如何使用多进程进行图像处理:

from PIL import Image
import numpy as np
from multiprocessing import Pool

# 图像缩放函数
def resize(img_path, size):
    img = Image.open(img_path)
    img = img.resize(size)
    return img

if __name__ == '__main__':
    # 加载图像列表
    img_list = ['image1.jpg', 'image2.jpg', 'image3.jpg', 'image4.jpg']
    
    # 创建进程池对象
    with Pool(processes=4) as pool:
        # 调用map方法执行任务
        results = pool.starmap(resize, [(img_path, (512, 512)) for img_path in img_list])
        
    # 将结果保存到文件
    for i, img in enumerate(results):
        img.save('result%d.jpg' % (i+1))

上面的代码中,我们首先定义了一个函数resize,用于对一张图片进行缩放操作。然后,我们使用PIL.Image.open()函数加载了4张图片,并使用进程池对它们进行缩放操作。最后,我们将结果保存到文件中。

由于进程池使用了4个进程同时进行图像处理,因此可以大大提高图像处理效率。

七、总结

本文介绍了Python中的多进程编程,并详细讲解了进程的基本概念、如何创建和启动进程、进程间通信、进程池等内容。同时,我们还演示了一个简单的实例,展示了如何使用多进程来加速图像处理任务。

多进程编程是Python中非常重要的话题之一,掌握多进程编程对于开发高性能、高并发应用具有重要意义。希望本文能够帮助读者更好地理解并掌握Python多进程编程技巧。

协程和异步编程
一、异步编程概述

异步编程是一种并发处理的方式,它在单线程下实现多个任务同时执行的效果,从而提高程序的整体性能。在传统的同步编程模型中,一个任务必须等待另一个任务完成后才能继续执行,这种模型会造成大量的 CPU 时间浪费。而在异步编程模型中,任务可以像轮流使用 CPU 一样交替执行,从而避免了 CPU 的空闲时间。

Python 的异步编程主要通过 asyncio 模块来实现,它提供了对协程的支持,使得开发者可以用比较简单的语法来编写高效的异步程序。在 asyncio 中,协程是异步编程的基本单位,可以理解为一个特殊的函数。

二、协程概述

协程是一种能够暂停执行并在需要时恢复执行的函数,它不同于线程和进程的异步编程方式。协程可以看作是一种用户级线程,也称为“微线程”,通常运行在单线程中,由协程调度程序切换执行。

Python 3.4 引入了 asyncio 库,内置了协程功能,此后 Python 开始支持原生的协程编程。在 Python 3.5 中,新引入了 async 和 await 两个关键字,更加方便了协程的编写。具体来说,async 关键字用于声明一个函数为协程函数,而 await 关键字用于暂停当前协程的执行,等待其他协程或者异步任务执行完成后再继续执行。

在 asyncio 中,协程由 asyncio.coroutine 装饰器进行修饰,这样被修饰的函数就可以使用 await 关键字暂停当前协程的执行了。下面是一个简单的协程示例:

import asyncio

async def coroutine():
    print("start coroutine")
    await asyncio.sleep(1)
    print("end coroutine")

# 初始化事件循环
loop = asyncio.get_event_loop()

# 执行协程
loop.run_until_complete(coroutine())

上面的代码中,我们首先定义了一个名为 coroutine 的协程函数,它打印出一条开始执行的消息,并调用 asyncio.sleep() 函数让协程挂起 1 秒钟,最后打印出一条结束执行的消息。

在主程序中,我们通过 asyncio.get_event_loop() 函数获取到一个事件循环对象,然后调用该对象的 run_until_complete() 方法来运行协程。

三、协程与线程的比较

协程和线程都可以实现异步编程,但二者有很大的不同之处。下面是协程与线程的比较:

  1. 状态切换方式不同:线程是由操作系统内核来进行调度的,而协程则是由程序员自己实现的调度方式。
  2. 调度开销不同:线程切换时需要保存当前上下文并在下一次切换时恢复,这个过程需要进行上下文切换和内核态和用户态之间的转换,因此比较耗费 CPU 时间。而协程的上下文切换仅仅是程序栈的切换,没有内核态和用户态之间的切换,因此非常快速。
  3. 并发性不同:线程是操作系统级别的并发,可以利用多核 CPU 实现真正的并行处理;而协程则是单线程的并发,它不能充分利用多核 CPU 的优势,但有着更高的并发性能。
  4. 编写难度不同:线程编程的难度较大,需要考虑线程安全和锁的问题,而协程编程则相对容易一些,不需要考虑这些问题。

综合来看,协程比线程更适用于 I/O 密集型应用程序,而线程则适用于 CPU 密集型应用程序。

四、asyncio 库的使用

  1. 事件循环

事件循环是 asyncio 的核心组件,它可以在单线程下运行多个协程,并负责调度它们的执行。每个 asyncio 程序必须有一个事件循环对象,我们可以通过 asyncio.get_event_loop() 函数获取到它。

import asyncio

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行协程
loop.run_until_complete(coroutine())

  1. 协程函数

协程函数是由 async 关键字修饰的函数,它可以被 await 关键字挂起和恢复执行。下面是一个简单的协程函数示例:

import asyncio

async def coroutine():
    print("start coroutine")
    await asyncio.sleep(1)
    print("end coroutine")

  1. 异步任务

异步任务是一个可等待对象,它可以被事件循环挂起和恢复执行。在 asyncio 中,常见的异步任务包括协程、Future 对象、Task 对象等。下面是异步任务的一个简单示例:

import asyncio

async def coroutine():
    print("start coroutine")
    await asyncio.sleep(1)
    print("end coroutine")

async def async_func():
    print("start async function")
    await coroutine()
    print("end async function")

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行异步任务
loop.run_until_complete(async_func())

  1. Future 对象

Future 是一个代表异步操作结果的对象,它可以被用于协程间通信和协调。如果一个协程需要等待另一个协程完成某个操作后才能继续执行,那么它可以通过 Future 对象来实现。下面是一个使用 Future 对象进行协程间通信的示例:

import asyncio

async def coroutine(future):
    print("start coroutine")
    await asyncio.sleep(1)
    future.set_result("hello world")
    print("end coroutine")

async def async_func():
    print("start async function")
    future = asyncio.Future()
    asyncio.ensure_future(coroutine(future))
    result = await future
    print(result)

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行异步任务
loop.run_until_complete(async_func())

在上面的代码中,我们首先定义了一个 coroutine 协程函数,并传入了一个 future 对象。在该函数内部,我们使用 asyncio.sleep() 函数模拟一个耗时的操作,并使用 future.set_result() 方法设置异步操作的返回值。在 async_func 函数中,我们监听 future 对象的完成状态,并在异步操作完成后打印出返回值。

  1. Task 对象

Task 对象是对 Future 对象的进一步封装,它可以方便地对多个协程进行调度和管理。Task 对象通常是通过 asyncio.create_task() 函数创建的。下面是一个使用 Task 对象进行协程调度的示例:

import asyncio

async def coroutine(name):
    print("start %s" % name)
    await asyncio.sleep(1)
    print("end %s" % name)

async def async_func():
    tasks = [asyncio.create_task(coroutine("coroutine%d" % i)) for i in range(3)]
    for task in tasks:
        await task

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行异步任务
loop.run_until_complete(async_func())

在上面的代码中,我们先定义了一个 coroutine 协程函数,并传入了一个名字参数。然后,在 async_func 函数内部,我们使用 asyncio.create_task() 函数创建了三个协程任务,并将它们添加到任务列表中。最后,我们使用 for 循环逐个执行每个任务。

  1. 同步与异步的混合使用

在实际应用中,我们常常需要在异步程序中调用同步函数或者阻塞操作,这时候就需要使用 asyncio 的一些辅助函数来避免阻塞事件循环。下面是一些常用的 asyncio 辅助函数:

  • asyncio.run_in_executor():在另一个线程或进程中执行函数,并将结果返回给当前协程。

import asyncio
import requests

async def coroutine():
    print("start coroutine")
    loop = asyncio.get_running_loop()
    future = loop.run_in_executor(None, requests.get, "https://www.baidu.com")
    response = await future
    print(response.status_code)
    print("end coroutine")

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行协程
loop.run_until_complete(coroutine())

  • asyncio.sleep():模拟一个耗时的操作,并挂起当前协程的执行。

import asyncio

async def coroutine():
    print("start coroutine")
    await asyncio.sleep(1)
    print("end coroutine")

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行协程
loop.run_until_complete(coroutine())

  • asyncio.wait():等待多个协程任务完成后再继续执行。

import asyncio

async def coroutine(name):
    print("start %s" % name)
    await asyncio.sleep(1)
    print("end %s" % name)

async def async_func():
    tasks = [asyncio.create_task(coroutine("coroutine%d" % i)) for i in range(3)]
    await asyncio.wait(tasks)

# 获取事件循环对象
loop = asyncio.get_event_loop()

# 执行异步任务
loop.run_until_complete(async_func())

五、总结

本文介绍了 Python 异步编程的基本概念,包括协程、事件循环、异步任务、Future 对象和 Task 对象等。我们还比较了协程与线程的不同之处,并且讲解了如何在异步程序中使用同步函数或阻塞操作。

异步编程可以大幅度提高程序的并发性能,特别适用于 I/O 密集型应用程序。Python 的 asyncio 模块为开发者提供了非常方便的协程支持,使得编写高效的异步程序变得简单易行。同时,在实际应用中需谨慎使用异步编程,避免造成过度使用导致的代码维护困难和可读性差等问题。

举报

相关推荐

0 条评论