龙空技术网

Python 并发编程

Kali与编程 385

前言:

而今同学们对“python 多并发”大致比较关切,我们都需要剖析一些“python 多并发”的相关资讯。那么小编在网上搜集了一些对于“python 多并发””的相关文章,希望我们能喜欢,大家快快来了解一下吧!

预计更新

1. Python 简介

- Python 简介和历史

- Python 特点和优势

- 安装 Python

2. 变量和数据类型

- 变量和标识符

- 基本数据类型:数字、字符串、布尔值等

- 字符串操作

- 列表、元组和字典

3. 控制语句和函数

- 分支结构:if/else 语句

- 循环结构:for 和 while 循环

- 函数

- 参数传递与返回值

- Lambda 表达式

4. 模块和文件 IO

- 模块的概念

- 导入模块

- 文件 IO

- 序列化和反序列化

5. 异常处理

- 异常简介

- try/except 语句

- 自定义异常

6. 面向对象编程

- 类和对象

- 继承和多态

- 属性和方法

- 抽象类和接口

7. 正则表达式

- 正则表达式概述

- 匹配和搜索

- 替换和分割

8. 并发编程

- 多线程

- 多进程

- 协程和异步编程

9. 数据库编程

- 关系型数据库介绍

- 使用 SQLite 数据库

- 使用 MySQL 数据库

- 使用 PostgreSQL 数据库

10. 网络编程

- Socket 编程简介

- TCP Socket 编程

- UDP Socket 编程

- HTTP 编程

11. Web 开发框架 Flask

- Flask 简介

- 安装 Flask

- 路由和视图函数

- 模板和静态文件

12. 数据分析和科学计算

- NumPy 基础

- Pandas 基础

- Matplotlib 基础

13. 机器学习入门

- 机器学习概述

- 监督学习和非监督学习

- Scikit-Learn 简介

- 利用 Scikit-Learn 进行数据预处理和模型训练

14. 自然语言处理

- 自然语言处理概述

- 中文分词和处理

- 文本分类和情感分析

15. 游戏开发与 Pygame

- Pygame 简介

- Pygame 基础

- 开发一个简单的游戏

8. 并发编程

- 多线程

- 多进程

- 协程和异步编程

多线程

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

1. 线程和进程的区别

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

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

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

2. Python threading模块

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

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

`start()`:启动线程。

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

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

3. 创建线程

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

```

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

```

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

示例代码如下:

```python

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()`方法等待它们运行结束。

4. 线程同步

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

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

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

4.1 Lock

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

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

```python

lock = threading.Lock()

```

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

```python

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()。

```python

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模块来创建:

```python

semaphore = threading.Semaphore(value)

```

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

```python

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()方法等待事件的发生。

```python

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模块来创建:

```python

cond = threading.Condition(lock=None)

```

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

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

```python

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发出通知才能继续执行。

5. 线程安全问题

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

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

1. 定义共享资源并初始化。

2. 创建互斥锁或信号量等同步机制。

3. 在对共享资源进行操作前获取锁或信号量。

4. 对共享资源进行操作。

5. 释放锁或信号量。

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

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

6. 总结

本文介绍了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:

```python

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类创建和启动子进程:

```python

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进行进程间通信:

```python

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进行进程间通信:

```python

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_conn`和`child_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()`等方法来提交任务,并返回结果。

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

```python

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()等方法,具体用法可以参考官方文档。

六、多进程编程实践

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

```python

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 关键字暂停当前协程的执行了。下面是一个简单的协程示例:

```python

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() 函数获取到它。

```python

import asyncio

# 获取事件循环对象

loop = asyncio.get_event_loop()

# 执行协程

loop.run_until_complete(coroutine())

```

2. 协程函数

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

```python

import asyncio

async def coroutine():

print("start coroutine")

await asyncio.sleep(1)

print("end coroutine")

```

3. 异步任务

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

```python

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())

```

4. Future 对象

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

```python

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 对象的完成状态,并在异步操作完成后打印出返回值。

5. Task 对象

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

```python

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 循环逐个执行每个任务。

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

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

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

```python

import asyncio

import requests

async def coroutine():

print("start coroutine")

loop = asyncio.get_running_loop()

future = loop.run_in_executor(None, requests.get, ";)

response = await future

print(response.status_code)

print("end coroutine")

# 获取事件循环对象

loop = asyncio.get_event_loop()

# 执行协程

loop.run_until_complete(coroutine())

```

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

```python

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():等待多个协程任务完成后再继续执行。

```python

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 模块为开发者提供了非常方便的协程支持,使得编写高效的异步程序变得简单易行。同时,在实际应用中需谨慎使用异步编程,避免造成过度使用导致的代码维护困难和可读性差等问题。

精彩继续:「链接」

标签: #python 多并发