前言
回顾昨天的内容,昨天从基础入门,列表与元组,字符串,字典,条件循环和其他语句,函数,面向对象编程,异常与文件处理等八个方向讲述了python语法编程,今天来到第二章python并发编程
从以下四个方向展开讲述:
- 网络编程
- 多线程
- 多进程
- 协程
第一章:网络编程
1.初网络编程
网络编程是指使用计算机网络进行通信的编程技术。在Python中,可以使用socket
模块来实现网络编程。
socket
是一个封装了TCP/IP
协议的网络编程库,它提供了一种通用的网络编程接口,可以用于创建客户端和服务器端程序。在Python中,可以使用socket
模块来创建socket
对象,然后使用该对象进行网络通信。
下面是一个简单的网络编程示例,其中创建了一个服务器端和一个客户端,客户端向服务器端发送消息,服务器端接收到消息后将其打印出来:
服务器端代码:
import socket
# 创建socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定IP地址和端口号
server_socket.bind(('127.0.0.1', 8888))
# 监听端口
server_socket.listen(5)
print('服务器已启动,等待客户端连接...')
# 等待客户端连接
client_socket, client_address = server_socket.accept()
print('客户端已连接,地址为:', client_address)
# 接收客户端消息
data = client_socket.recv(1024)
print('接收到客户端消息:', data.decode())
# 关闭socket连接
client_socket.close()
server_socket.close()
客户端代码:
import socket
# 创建socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接服务器
client_socket.connect(('127.0.0.1', 8888))
# 发送消息
client_socket.send('Hello, server!'.encode())
# 关闭socket连接
client_socket.close()
在PyCharm
中编译和运行Matlab
文件,需要先安装Matlab
并配置好环境变量。然后,在PyCharm
中打开Matlab
文件,点击运行按钮即可编译和运行Matlab
文件。如果需要传递参数,可以在运行配置中设置。
2.TCP/IP简介
TCP/IP
是一种网络协议,它是互联网的基础协议。TCP/IP
协议族包括了许多协议,其中最重要的是TCP
和IP
协议。
TCP
协议是一种面向连接的协议,它提供了可靠的数据传输服务。TCP
协议通过三次握手建立连接,然后通过数据分段和确认机制来保证数据的可靠传输。
IP
协议是一种无连接的协议,它提供了数据包的传输服务。IP协议通过路由选择算法来确定数据包的传输路径,然后将数据包传输到目的地。
在网络编程中,我们通常使用TCP
协议来进行数据传输。TCP
协议提供了可靠的数据传输服务,适用于需要保证数据传输可靠性的场景,如文件传输、邮件传输等。而IP
协议则适用于需要快速传输数据的场景,如视频流传输、实时通信等。
3.网络设计模块
1.Socket简介
Socket
是一种通信机制,它允许不同的进程在网络上进行通信。在Python中,Socket
是通过socket
模块来实现的。Socket
通常用于客户端和服务器之间的通信,但也可以用于进程之间的通信。
2.Socket模块使用
Python中的socket
模块提供了一组函数和类,用于创建和操作Socket
。常用的函数和类包括:
socket()
:创建一个Socket对象。bind()
:将Socket绑定到一个特定的地址和端口。listen()
:开始监听连接请求。accept()
:接受一个连接请求,并返回一个新的Socket对象。connect()
:连接到一个远程Socket。send()
:发送数据。recv()
:接收数据。
3.服务器
在Python中,可以使用socket
模块创建一个服务器。服务器通常需要绑定到一个特定的地址和端口,并监听连接请求。当有客户端连接到服务器时,服务器会接受连接请求,并创建一个新的Socket
对象来处理客户端请求。
以下是一个简单的Python服务器示例:
import socket
HOST = '127.0.0.1' # 服务器地址
PORT = 8888 # 服务器端口号
# 创建一个Socket对象
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 绑定到地址和端口
server_socket.bind((HOST, PORT))
# 开始监听连接请求
server_socket.listen(1)
print('Server is running on {}:{}'.format(HOST, PORT))
while True:
# 接受一个连接请求,并返回一个新的Socket对象
client_socket, client_address = server_socket.accept()
print('Client connected from {}:{}'.format(client_address[0], client_address[1]))
# 处理客户端请求
data = client_socket.recv(1024)
print('Received data: {}'.format(data.decode()))
# 发送响应数据
response = 'Hello, client!'
client_socket.send(response.encode())
# 关闭连接
client_socket.close()
4.客户端
在Python中,可以使用socket
模块创建一个客户端。客户端通常需要连接到一个远程Socket
,并发送请求数据。当服务器响应请求时,客户端会接收响应数据。
以下是一个简单的Python客户端示例:
import socket
HOST = '127.0.0.1' # 服务器地址
PORT = 8888 # 服务器端口号
# 创建一个Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到远程Socket
client_socket.connect((HOST, PORT))
# 发送请求数据
request = 'Hello, server!'
client_socket.send(request.encode())
# 接收响应数据
response = client_socket.recv(1024)
print('Received data: {}'.format(response.decode()))
# 关闭连接
client_socket.close()
5.文件下载器
文件下载器是一个常见的网络应用程序,它可以从远程服务器下载文件并保存到本地。在Python中,可以使用socket
模块和urllib
模块来实现文件下载器。
以下是一个简单的Python文件下载器示例:
import socket
import urllib.request
HOST = '127.0.0.1' # 服务器地址
PORT = 8888 # 服务器端口号
FILE_URL = 'http://example.com/file.txt' # 文件下载地址
FILE_NAME = 'file.txt' # 文件保存路径
# 创建一个Socket对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到远程Socket
client_socket.connect((HOST, PORT))
# 发送请求数据
request = 'GET {}\r\n'.format(FILE_URL)
client_socket.send(request.encode())
# 接收响应数据
response = client_socket.recv(1024)
print('Received data: {}'.format(response.decode()))
# 下载文件并保存到本地
with open(FILE_NAME, 'wb') as f:
while True:
data = client_socket.recv(1024)
if not data:
break
f.write(data)
# 关闭连接
client_socket.close()
4.补充内容
1.网络编程中的UDP协议
UDP(User Datagram Protocol)是一种无连接的、不可靠的传输协议,它不保证数据包的可靠性和顺序性,但是传输速度快,适用于一些对数据可靠性要求不高的应用场景,如视频、音频等实时传输。
在Python中,使用socket
模块可以实现UDP
协议的网络编程。下面是一个简单的UDP
服务器和客户端的示例:
UDP
服务器:
import socket
# 创建UDP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定IP地址和端口号
server_socket.bind(('127.0.0.1', 8888))
while True:
# 接收数据
data, addr = server_socket.recvfrom(1024)
print('Received from %s:%s.' % addr)
print('Data: %s' % data.decode())
# 发送数据
server_socket.sendto('Hello, client!'.encode(), addr)
UDP
客户端:
import socket
# 创建UDP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发送数据
client_socket.sendto('Hello, server!'.encode(), ('127.0.0.1', 8888))
# 接收数据
data, addr = client_socket.recvfrom(1024)
print('Received from %s:%s.' % addr)
print('Data: %s' % data.decode())
# 关闭套接字
client_socket.close()
在UDP
协议中,发送数据时需要指定目标地址和端口号,接收数据时会返回发送方的地址和端口号。由于UDP
协议不保证数据的可靠性和顺序性,因此在实际应用中需要考虑数据丢失、重复、乱序等问题。
2.UDP协议与TCP协议的区别
UDP
协议和TCP
协议是两种常用的网络传输协议,它们有以下几点区别:
-
连接方式:
TCP
协议是面向连接的协议,而UDP
协议是无连接的协议。TCP
协议在传输数据之前需要先建立连接,而UDP
协议不需要。 -
可靠性:
TCP
协议是可靠的协议,它保证数据的可靠传输,而UDP
协议是不可靠的协议,它不保证数据的可靠传输。 -
传输效率:
UDP
协议比TCP
协议传输效率高,因为UDP
协议不需要建立连接和维护连接状态,而TCP
协议需要。 -
数据包大小:
UDP
协议传输的数据包大小限制为64KB,而TCP
协议没有限制。 -
应用场景:
TCP
协议适用于对数据传输可靠性要求较高的场景,如文件传输、邮件传输等;而UDP
协议适用于对数据传输实时性要求较高的场景,如视频直播、语音通话等。
3.UDP协议代码实现方式
UDP
协议是一种无连接的协议,它不保证数据传输的可靠性和顺序性,但是具有传输速度快的优点。下面是Python中使用UDP
协议进行网络编程的代码实现方式:
服务器端代码:
import socket
# 创建UDP套接字
server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 绑定IP地址和端口号
server_socket.bind(('127.0.0.1', 8888))
# 接收数据
while True:
data, addr = server_socket.recvfrom(1024)
print('Received from %s:%s.' % addr)
print('Data: %s' % data.decode())
# 发送数据
server_socket.sendto('Hello, client!'.encode(), addr)
客户端代码:
import socket
# 创建UDP套接字
client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 发送数据
client_socket.sendto('Hello, server!'.encode(), ('127.0.0.1', 8888))
# 接收数据
data, addr = client_socket.recvfrom(1024)
print('Received from %s:%s.' % addr)
print('Data: %s' % data.decode())
# 关闭套接字
client_socket.close()
在上面的代码中,服务器端首先创建了一个UDP
套接字,并绑定了IP
地址和端口号。然后通过recvfrom()
方法接收客户端发送的数据,并通过sendto()
方法向客户端发送数据。客户端同样创建了一个UDP
套接字,并通过sendto()
方法向服务器端发送数据,然后通过recvfrom()
方法接收服务器端发送的数据。最后,客户端关闭套接字。
第二章:多线程
1.线程和进程
线程和进程是操作系统中的两个重要概念,它们都是并发编程的基础。线程是操作系统能够进行运算调度的最小单位,而进程则是操作系统进行资源分配和调度的基本单位。
线程和进程的区别:
- 线程是进程的一部分,一个进程可以包含多个线程,而一个线程只能属于一个进程。
- 进程拥有独立的内存空间,而线程共享进程的内存空间。
- 进程之间的通信需要使用IPC(Inter-Process Communication)机制,而线程之间可以直接共享数据。
- 进程的创建和销毁比线程慢,因为进程需要分配和释放独立的内存空间,而线程只需要分配和释放一些寄存器和栈空间。
在Python中,可以使用threading
模块来创建和管理线程。下面是一个简单的线程示例:
import threading
def worker():
print('Worker thread started')
# do some work here
print('Worker thread finished')
# create a new thread
t = threading.Thread(target=worker)
# start the thread
t.start()
# wait for the thread to finish
t.join()
在这个示例中,我们创建了一个名为worker
的函数,它将在一个新的线程中运行。我们使用threading.Thread
类创建了一个新的线程对象,并将worker
函数作为目标传递给它。然后,我们使用start()
方法启动线程,并使用join()
方法等待线程完成。
2.使用线程
在Python中,使用线程可以通过threading
模块来实现。下面是一个简单的例子,展示了如何使用线程:
import threading
def worker():
"""线程执行的任务"""
print("Worker thread started")
# 执行一些任务
print("Worker thread finished")
# 创建线程
t = threading.Thread(target=worker)
# 启动线程
t.start()
# 主线程继续执行其他任务
print("Main thread finished")
在上面的例子中,我们首先定义了一个worker
函数,它将在一个单独的线程中执行。然后,我们使用threading.Thread
类创建了一个新的线程,并将worker函数作为参数传递给它。最后,我们调用start
方法来启动线程。
注意,线程是异步执行的,因此主线程不会等待线程完成。在上面的例子中,主线程会立即继续执行,输出Main thread finished
。如果我们希望等待线程完成后再继续执行主线程,可以使用join
方法:
# 等待线程完成
t.join()
# 主线程继续执行其他任务
print("Main thread finished")
在上面的代码中,我们在启动线程后调用了t.join()方法,这将阻塞主线程,直到线程完成。然后,主线程才会继续执行。
3.多线程全局变量
在多线程编程中,多个线程可以共享全局变量。但是需要注意的是,多个线程同时对同一个全局变量进行读写操作时,可能会出现数据竞争(Data Race)的问题,导致程序出现不可预期的结果。
为了避免数据竞争,可以使用线程锁(Thread Lock)来保证同一时刻只有一个线程可以访问共享变量。Python中提供了Lock
、RLock
、Semaphore
等多种锁机制,可以根据实际需求选择合适的锁。
下面是一个使用Lock
来保证多线程共享全局变量安全的示例代码:
import threading
# 定义全局变量
count = 0
# 定义线程锁
lock = threading.Lock()
# 定义线程函数
def add():
global count
for i in range(100000):
# 获取锁
lock.acquire()
count += 1
# 释放锁
lock.release()
# 创建两个线程
t1 = threading.Thread(target=add)
t2 = threading.Thread(target=add)
# 启动线程
t1.start()
t2.start()
# 等待线程执行完毕
t1.join()
t2.join()
# 输出结果
print(count)
在上面的代码中,我们定义了一个全局变量count
,并使用Lock
来保证多个线程对count
进行读写操作时的安全性。在每个线程中,我们首先获取锁,然后对count
进行加1操作,最后释放锁。这样就可以保证同一时刻只有一个线程可以访问count
,避免了数据竞争的问题。
需要注意的是,使用锁会带来一定的性能损失,因为每次获取锁和释放锁都需要一定的时间。因此,在实际应用中,需要根据实际情况来选择合适的锁机制,避免过度使用锁导致程序性能下降。
4.共享全局变量所带来的问题
在多线程编程中,多个线程可以共享全局变量。但是,共享全局变量也会带来一些问题:
-
竞争条件:当多个线程同时访问和修改同一个全局变量时,可能会出现竞争条件,导致程序出现不可预测的结果。
-
数据不一致:当多个线程同时修改同一个全局变量时,可能会导致数据不一致的问题,即某些线程看到的变量值与其他线程看到的不同。
-
死锁:当多个线程同时等待对方释放某个资源时,可能会出现死锁的情况,导致程序无法继续执行。
因此,在多线程编程中,需要注意对共享全局变量的访问和修改,避免出现上述问题。可以使用锁、条件变量等机制来保证线程之间的同步和互斥。
5.解决线程同时修改全局变量的方式
在多线程编程中,共享全局变量可能会带来一些问题,例如:
-
竞争条件:多个线程同时修改同一个全局变量,可能会导致数据不一致或者出现意料之外的结果。
-
死锁:多个线程同时等待对方释放资源,导致程序无法继续执行。
为了解决这些问题,可以采用以下方式:
-
使用锁:在访问共享变量时,使用锁来保证同一时刻只有一个线程可以修改变量。Python中提供了threading模块中的Lock类来实现锁。
-
使用线程安全的数据结构:Python中提供了一些线程安全的数据结构,例如Queue、deque等,可以在多线程环境下安全地访问和修改数据。
-
使用局部变量:将全局变量作为参数传递给线程函数,让线程函数在局部变量上进行操作,避免多个线程同时修改同一个全局变量。
-
使用线程本地存储:Python中提供了threading模块中的local类,可以在每个线程中创建一个独立的变量,避免多个线程之间共享变量。
6.互斥锁
在多线程编程中,互斥锁是一种常用的同步机制,用于保护共享资源,防止多个线程同时修改同一个变量导致数据不一致的问题。
互斥锁的基本思想是,在访问共享资源之前,先获取锁,如果锁已经被其他线程获取,则当前线程会被阻塞,直到锁被释放为止。在访问完共享资源之后,释放锁,让其他线程可以获取锁并访问共享资源。
在Python中,可以使用threading
模块中的Lock
类来实现互斥锁。Lock
类有两个基本方法:
acquire([blocking])
:获取锁,如果锁已经被其他线程获取,则当前线程会被阻塞。如果blocking
为False
,则获取锁失败时会立即返回False
,而不是阻塞等待。release()
:释放锁,让其他线程可以获取锁并访问共享资源。 下面是一个使用互斥锁的例子:
import threading
# 共享变量
count = 0
# 创建互斥锁
lock = threading.Lock()
# 线程函数
def worker():
global count
for i in range(100000):
# 获取锁
lock.acquire()
try:
count += 1
finally:
# 释放锁
lock.release()
# 创建多个线程
threads = []
for i in range(10):
t = threading.Thread(target=worker)
threads.append(t)
# 启动线程
for t in threads:
t.start()
# 等待所有线程执行完毕
for t in threads:
t.join()
# 输出结果
print(count)
在上面的例子中,我们创建了一个共享变量count
,并使用互斥锁来保护它。在每个线程中,我们先获取锁,然后修改count
的值,最后释放锁。这样就可以保证多个线程不会同时修改count
的值,从而避免了数据不一致的问题。最后输出count
的值,可以看到它的值为1000000,符合预期。
7.死锁
死锁是指两个或多个线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法继续执行下去。在多线程编程中,死锁是一种常见的问题,需要特别注意。
死锁的产生通常需要满足以下四个条件:
- 互斥条件:一个资源每次只能被一个线程使用。
- 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺,只能由该线程自己释放。
- 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源的关系。
为了避免死锁的产生,可以采用以下几种方式:
- 避免使用多个锁,尽量使用一个锁来控制多个资源的访问。
- 避免持有锁的时间过长,尽量缩短锁的持有时间。
- 避免循环等待,尽量按照固定的顺序获取锁。
- 使用超时机制,当等待时间超过一定时间后,自动释放锁,避免长时间等待造成的死锁。
8.线程池
线程池是一种线程管理技术,它可以在程序启动时创建一定数量的线程,放入一个池中,当需要使用线程时,就从池中取出一个线程执行任务,任务执行完毕后,线程并不会被销毁,而是放回池中等待下一次任务的到来。
使用线程池可以避免频繁创建和销毁线程的开销,提高程序的性能和效率。在Python中,可以使用标准库中的concurrent.futures
模块来实现线程池。
下面是一个简单的线程池示例:
import concurrent.futures
import time
def worker(num):
print(f"Thread-{num} started")
time.sleep(1)
print(f"Thread-{num} finished")
if __name__ == '__main__':
with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
for i in range(5):
executor.submit(worker, i)
在这个示例中,我们创建了一个包含3个线程的线程池,然后提交了5个任务给线程池执行。由于线程池中只有3个线程,因此只有3个任务会同时执行,其余的任务会等待空闲线程的出现。
输出结果如下:
Thread-0 started
Thread-1 started
Thread-2 started
Thread-0 finished
Thread-3 started
Thread-1 finished
Thread-4 started
Thread-2 finished
Thread-3 finished
Thread-4 finished
可以看到,线程池中的3个线程依次执行了5个任务,任务的执行顺序与提交的顺序无关。
第三章:多进程
1.进程的状态
在操作系统中,进程有以下几种状态:
-
就绪状态(Ready):进程已经准备好运行,等待分配CPU时间片。
-
运行状态(Running):进程正在运行,占用CPU时间片。
-
阻塞状态(Blocked):进程因为某些原因无法继续执行,例如等待I/O操作完成或等待某个资源的释放。
-
挂起状态(Suspended):进程被暂停,不再占用CPU时间片,但是它的状态信息仍然保存在内存中。
-
终止状态(Terminated):进程已经完成执行或被强制终止。
进程的状态转换通常是由操作系统内核进行控制的,例如当一个进程等待I/O操作完成时,操作系统会将该进程的状态从就绪状态转换为阻塞状态,当I/O操作完成后,操作系统会将该进程的状态从阻塞状态转换为就绪状态,等待分配CPU时间片。
2.线程的创建-multiprocessing
在 Python 中,可以使用 multiprocessing 模块来创建多进程。multiprocessing 模块提供了一个 Process 类,可以用来创建进程。下面是一个简单的例子:
import multiprocessing
def worker():
"""子进程要执行的任务"""
print('Worker')
if __name__ == '__main__':
# 创建子进程
p = multiprocessing.Process(target=worker)
# 启动子进程
p.start()
# 等待子进程结束
p.join()
在上面的例子中,我们首先定义了一个 worker
函数,它是子进程要执行的任务。然后,我们使用 multiprocessing.Process
类创建了一个子进程,并将 worker
函数作为参数传递给了 Process
类的构造函数。接着,我们调用 start
方法启动子进程,最后调用 join
方法等待子进程结束。
需要注意的是,在 Windows 系统中,由于 multiprocessing
模块使用了 fork
系统调用,而 Windows 不支持 fork
,因此需要在 if __name__ == '__main__':
语句中调用子进程的代码。这是因为在 Windows 中,每个进程都会执行一遍程序的所有代码,而 if __name__ == '__main__':
语句可以保证子进程只会执行指定的代码。
3.进程丶线程对比
进程和线程都是实现并发编程的方式,但是它们有以下不同点:
-
资源占用:进程拥有独立的内存空间,而线程共享进程的内存空间。因此,创建进程的开销比创建线程大,同时进程间的通信也比线程间的通信复杂。
-
并发性:由于线程共享进程的内存空间,因此线程间的通信和数据共享比进程间的通信和数据共享更容易。同时,线程的切换比进程的切换更快,因此线程的并发性比进程高。
-
安全性:由于线程共享进程的内存空间,因此多个线程同时访问同一块内存时,可能会出现竞争条件,导致数据不一致或者程序崩溃。而进程之间的内存空间是独立的,因此进程间的数据不会相互影响。
-
编程复杂度:由于进程间的通信和数据共享比较复杂,因此编写多进程程序的复杂度比编写多线程程序的复杂度高。
总的来说,进程适合于CPU密集型任务,而线程适合于IO密集型任务。在实际应用中,需要根据具体的场景选择合适的并发编程方式。
4.进程间的通信-Queue
在多进程编程中,不同进程之间的数据是无法直接共享的,因为每个进程都有自己独立的内存空间。因此,为了实现进程间的通信,我们需要使用一些特殊的机制。
其中,最常用的进程间通信方式是使用队列(Queue)。队列是一种先进先出(FIFO)的数据结构,可以用来在多个进程之间传递数据。
在Python中,我们可以使用multiprocessing
模块中的Queue类来实现进程间通信。Queue类提供了put()
和get()
方法,用于向队列中添加数据和从队列中取出数据。
下面是一个简单的例子,演示了如何使用Queue实现进程间通信:
from multiprocessing import Process, Queue
def worker(q):
while True:
item = q.get()
if item is None:
break
print(item)
if __name__ == '__main__':
q = Queue()
p = Process(target=worker, args=(q,))
p.start()
for i in range(10):
q.put(i)
q.put(None)
p.join()
在这个例子中,我们创建了一个进程p,它的工作是从队列q中取出数据并打印出来。主进程向队列中添加了10个数据,然后再添加一个None,表示数据已经全部添加完毕。最后,主进程等待进程p执行完毕。
需要注意的是,当我们向队列中添加数据时,如果队列已满,put()方法会阻塞,直到队列中有空闲位置。同样地,当我们从队列中取出数据时,如果队列为空,get()方法也会阻塞,直到队列中有数据可取。
除了Queue之外,Python中还提供了一些其他的进程间通信方式,比如Pipe
、Value
和Array
等。这些方式各有特点,可以根据具体的需求选择合适的方式
5.进程池的创建-pool
在Python中,我们可以使用multiprocessing
模块中的Pool
类来创建进程池。进程池是一组可重用的进程,可以在需要时分配给任务。这样可以避免频繁地创建和销毁进程,从而提高程序的效率。
下面是一个使用进程池的例子:
import multiprocessing
def worker(num):
"""进程池中的任务"""
print('Worker %d is running' % num)
if __name__ == '__main__':
# 创建进程池,池中有3个进程
pool = multiprocessing.Pool(processes=3)
# 向进程池中添加任务
for i in range(5):
pool.apply_async(worker, args=(i,))
# 关闭进程池,不再接受新的任务
pool.close()
# 等待所有任务完成
pool.join()
print('All workers done.')
在这个例子中,我们首先创建了一个进程池,池中有3个进程。然后向进程池中添加了5个任务,每个任务都是调用worker
函数。最后,我们关闭了进程池,并等待所有任务完成。
需要注意的是,进程池中的任务必须是可序列化的,因为进程池会将任务发送给子进程执行。如果任务中包含不可序列化的对象,会导致进程池无法正常工作。
第四章:协程
协程是一种轻量级的线程,也称为微线程或者用户级线程。协程的特点是在一个线程中,可以有多个协程,协程之间可以相互切换,从而实现并发执行。
在Python中,协程是通过生成器实现的。通过yield关键字,可以将一个函数变成一个生成器,从而实现协程的功能。在协程中,可以使用yield关键字来暂停函数的执行,并返回一个值给调用者。当协程再次被调用时,可以从上一次暂停的位置继续执行。
Python中的协程有两种实现方式:使用生成器实现的协程和使用async/await
关键字实现的协程。
使用生成器实现的协程:
def coroutine():
while True:
value = yield
print('Received value:', value)
c = coroutine()
next(c) # 启动协程
c.send(10) # 发送值给协程
使用async/await关键字实现的协程:
import asyncio
async def coroutine():
while True:
value = await asyncio.sleep(1)
print('Received value:', value)
loop = asyncio.get_event_loop()
loop.run_until_complete(coroutine())
在使用async/await关键字实现的协程中,需要使用asyncio模块提供的事件循环来运行协程。在协程中,可以使用await关键字来暂停函数的执行,并等待一个异步操作完成。当异步操作完成后,协程会从await语句处继续执行。
协程的优点是可以避免线程切换的开销,从而提高程序的性能。同时,协程也可以避免线程之间的竞争条件和死锁问题。但是,协程也有一些缺点,例如不能利用多核CPU的优势,以及不能进行阻塞式IO操作。
1.协程的意义
协程是一种轻量级的线程,可以在单个线程中实现并发。与线程相比,协程的切换开销更小,可以更高效地利用CPU资源。协程的意义在于:
-
提高程序的并发性能:协程可以在单个线程中实现并发,避免了线程切换的开销,提高了程序的并发性能。
-
简化编程模型:协程可以使用同步的编程模型,避免了复杂的线程同步问题,使得编程更加简单。
-
支持高并发:协程可以支持大量的并发任务,可以用于高并发的网络编程、爬虫等场景。
-
提高代码可读性:协程可以使用同步的编程模型,代码可读性更高,易于维护。
总之,协程是一种高效、简单、可读性强的并发编程模型,可以提高程序的并发性能,支持高并发,简化编程模型。
2.asyncio事件循环
在Python中,协程是一种轻量级的并发编程方式,它可以在单线程中实现并发执行。协程的意义在于可以提高程序的并发性能,减少线程切换的开销,同时也可以简化编程模型,使得代码更加易于理解和维护。
在Python 3.4及以上版本中,标准库中提供了asyncio
模块,它是Python中实现协程的主要方式之一。asyncio
模块提供了一个事件循环(Event Loop
),它可以在单线程中实现多个协程的并发执行。事件循环会不断地从协程队列中取出协程并执行,当协程遇到IO操作时,会自动挂起并切换到其他协程执行,等待IO操作完成后再恢复执行。
asyncio
事件循环的使用方式如下:
1.创建一个事件循环对象:
import asyncio
loop = asyncio.get_event_loop()
2.将协程对象加入事件循环中:
async def coroutine():
# 协程代码
loop.run_until_complete(coroutine())
3.启动事件循环:
loop.run_forever()
在事件循环中,可以使用async/await
关键字定义协程函数,使用asyncio
模块提供的各种方法实现协程之间的通信和协作。例如,可以使用asyncio.sleep()
方法实现协程的延时操作,使用asyncio.wait()
方法等待多个协程的完成等。
3.await关键字
在Python中,await
是一个关键字,用于等待一个协程完成。当一个协程调用另一个协程时,它可以使用await
关键字来等待另一个协程完成并返回结果。在等待期间,当前协程会被挂起,直到被等待的协程完成。
例如,假设有两个协程A和B,A需要等待B完成后才能继续执行。在协程A中,可以使用await
关键字来等待协程B完成:
async def coroutine_b():
# 协程B的代码
async def coroutine_a():
# 协程A的代码
result = await coroutine_b()
# 继续执行协程A的代码
在这个例子中,当协程A调用await coroutine_b()
时,它会等待协程B完成并返回结果。在等待期间,协程A会被挂起,直到协程B完成。一旦协程B完成并返回结果,协程A会继续执行。
使用await关键字可以使协程之间的调用更加简洁和直观,同时也可以避免使用回调函数等复杂的异步编程模式。
4.concurrent和future对象
在Python中,asyncio
模块提供了一种基于协程的异步编程方式。在协程中,我们可以使用async/await
关键字来定义异步函数,使用asyncio
模块提供的事件循环来调度协程的执行。
除了协程之外,asyncio
还提供了一些其他的并发编程工具,包括concurrent
和future
对象。
- concurrent对象
concurrent
对象是asyncio
中的一个重要概念,它表示一个协程的执行状态。在asyncio
中,我们可以使用asyncio.create_task()
函数来创建一个concurrent
对象,该函数接受一个协程对象作为参数,并返回一个concurrent
对象。
例如,下面的代码创建了一个协程对象,并使用create_task()
函数将其转换为concurrent对象:
import asyncio
async def my_coroutine():
print('Coroutine started')
await asyncio.sleep(1)
print('Coroutine ended')
async def main():
task = asyncio.create_task(my_coroutine())
await task
asyncio.run(main())
在上面的代码中,我们使用create_task()
函数将my_coroutine()
函数转换为concurrent
对象,并将其赋值给task变量。然后,我们使用await
关键字等待task对象的完成。
- future对象
future
对象是asyncio
中的另一个重要概念,它表示一个异步操作的结果。在asyncio
中,我们可以使用asyncio.Future()
函数来创建一个future
对象,该函数返回一个未完成的future
对象。
例如,下面的代码创建了一个未完成的future对象:
import asyncio
async def my_coroutine():
print('Coroutine started')
await asyncio.sleep(1)
print('Coroutine ended')
return 'Result'
async def main():
future = asyncio.Future()
await asyncio.sleep(1)
future.set_result(await my_coroutine())
print(future.result())
asyncio.run(main())
在上面的代码中,我们使用asyncio.Future()
函数创建了一个未完成的future
对象,并将其赋值给future
变量。然后,我们使用await
关键字等待1秒钟,然后调用my_coroutine()
函数,并将其结果设置为future
对象的结果。最后,我们打印future对象的结果。
5.asyncio异步迭代器和上下文管理
除了concurrent和future对象之外,asyncio还提供了一些其他的并发编程工具,包括异步迭代器和上下文管理。
异步迭代器是一种特殊的迭代器,它可以在异步环境中使用。在asyncio中,我们可以使用async for循环来遍历异步迭代器。
例如,下面的代码使用async for循环遍历一个异步迭代器:
import asyncio
async def my_coroutine():
for i in range(5):
await asyncio.sleep(1)
yield i
async def main():
async for i in my_coroutine():
print(i)
asyncio.run(main())
在上面的代码中,我们定义了一个异步生成器函数my_coroutine()
,它使用yield
语句返回一个值,并在每次返回值之间暂停1秒钟。然后,我们使用async for
循环遍历my_coroutine()
函数返回的异步迭代器,并打印每个返回值。
上下文管理是一种在异步环境中管理资源的方式。在asyncio
中,我们可以使用async with
语句来管理异步上下文。
例如,下面的代码使用async with
语句管理一个异步上下文:
import asyncio
class MyContext:
async def __aenter__(self):
print('Entering context')
await asyncio.sleep(1)
return self
async def __aexit__(self, exc_type, exc, tb):
print('Exiting context')
await asyncio.sleep(1)
async def main():
async with MyContext() as context:
print('Inside context')
asyncio.run(main())
在上面的代码中,我们定义了一个MyContext
类,它实现了__aenter__()
和__aexit__()
方法。aenter()
方法在进入上下文时被调用,aexit()
方法在退出上下文时被调用。在main()
函数中,我们使用async with
语句管理MyContext
对象,并在上下文中打印一条消息。当我们进入和退出上下文时,aenter()
和__aexit__()
方法会被调用,并暂停1秒钟。
6.异步操作MySQL
在Python中,我们可以使用异步IO库asyncio
来实现异步操作MySQL数据库。下面是一个简单的示例:
import asyncio
import aiomysql
async def test_mysql():
# 连接MySQL数据库
conn = await aiomysql.connect(host='localhost', port=3306,
user='root', password='password',
db='test', charset='utf8mb4')
# 创建游标
cur = await conn.cursor()
# 执行SQL语句
await cur.execute("SELECT * FROM users")
# 获取查询结果
result = await cur.fetchall()
# 输出查询结果
print(result)
# 关闭游标和连接
await cur.close()
conn.close()
# 运行异步函数
loop = asyncio.get_event_loop()
loop.run_until_complete(test_mysql())
在上面的示例中,我们使用了aiomysql
库来连接MySQL数据库,并使用async/await
语法来执行异步操作。首先,我们使用aiomysql.connect()
方法来连接MySQL数据库,然后使用await conn.cursor()
方法创建游标,使用await cur.execute()
方法执行SQL语句,使用await cur.fetchall()
方法获取查询结果,最后使用await cur.close()
方法关闭游标,使用conn.close()
方法关闭连接。
需要注意的是,在使用aiomysql
库时,我们需要在连接MySQL数据库时指定charset='utf8mb4'
,以支持中文字符集。
7.异步爬虫
异步爬虫是指使用协程来实现爬虫程序,通过异步非阻塞的方式来提高爬取效率。在Python中,可以使用asyncio
库来实现异步爬虫。
下面是一个简单的异步爬虫示例:
import asyncio
import aiohttp
from bs4 import BeautifulSoup
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def get_links(session, url):
html = await fetch(session, url)
soup = BeautifulSoup(html, 'html.parser')
links = []
for link in soup.find_all('a'):
href = link.get('href')
if href and href.startswith('http'):
links.append(href)
return links
async def main():
async with aiohttp.ClientSession() as session:
links = await get_links(session, 'https://www.baidu.com')
for link in links:
print(link)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
在这个示例中,我们使用了aiohttp
库来发送异步HTTP请求,使用BeautifulSoup
库来解析HTML页面,使用asyncio
库来实现协程。
首先定义了一个fetch
函数,用于发送HTTP请求并返回响应内容。然后定义了一个get_links
函数,用于获取页面中的所有链接。最后,在main
函数中使用aiohttp
库创建一个异步HTTP客户端会话,调用get_links
函数获取链接,并打印出来。
需要注意的是,在使用aiohttp
库时,需要使用async with
语句来创建一个异步HTTP客户端会话,以确保会话在使用完毕后能够正确关闭。
关于Python学习指南
学好 Python 不论是就业还是做副业赚钱都不错,但要学会 Python 还是要有一个学习规划。最后给大家分享一份全套的 Python 学习资料,给那些想学习 Python 的小伙伴们一点帮助!
包括:Python激活码+安装包、Python web开发,Python爬虫,Python数据分析,人工智能、自动化办公等学习教程。带你从零基础系统性的学好Python!
👉Python所有方向的学习路线👈
Python所有方向路线就是把Python常用的技术点做整理,形成各个领域的知识点汇总,它的用处就在于,你可以按照上面的知识点去找对应的学习资源,保证自己学得较为全面。(全套教程文末领取)
👉Python学习视频600合集👈
观看零基础学习视频,看视频学习是最快捷也是最有效果的方式,跟着视频中老师的思路,从基础到深入,还是很容易入门的。
温馨提示:篇幅有限,已打包文件夹,获取方式在:文末
👉Python70个实战练手案例&源码👈
光学理论是没用的,要学会跟着一起敲,要动手实操,才能将自己的所学运用到实际当中去,这时候可以搞点实战案例来学习。
👉Python大厂面试资料👈
我们学习Python必然是为了找到高薪的工作,下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料,并且有阿里大佬给出了权威的解答,刷完这一套面试资料相信大家都能找到满意的工作。
👉Python副业兼职路线&方法👈
学好 Python 不论是就业还是做副业赚钱都不错,但要学会兼职接单还是要有一个学习规划。
👉 这份完整版的Python全套学习资料已经上传,朋友们如果需要可以扫描下方CSDN官方认证二维码免费领取【保证100%免费
】