上岸第一剑,编程语法必修:python并发编程

前言

回顾昨天的内容,昨天从基础入门,列表与元组,字符串,字典,条件循环和其他语句,函数,面向对象编程,异常与文件处理等八个方向讲述了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协议族包括了许多协议,其中最重要的是TCPIP协议。

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中提供了LockRLockSemaphore等多种锁机制,可以根据实际需求选择合适的锁。

下面是一个使用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]):获取锁,如果锁已经被其他线程获取,则当前线程会被阻塞。如果blockingFalse,则获取锁失败时会立即返回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中还提供了一些其他的进程间通信方式,比如PipeValueArray等。这些方式各有特点,可以根据具体的需求选择合适的方式

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还提供了一些其他的并发编程工具,包括concurrentfuture对象。

  • 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%免费

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/528079.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【CSDN活动】人工智能:前沿科技中的创业机遇与挑战

🌈个人主页: 鑫宝Code 🔥热门专栏: 闲话杂谈| 炫酷HTML | JavaScript基础 ​💫个人格言: "如无必要,勿增实体" 文章目录 人工智能:前沿科技中的创业机遇与挑战一、AI技术的快速发展与应用拓…

C++手撕红黑树

文章目录 红黑树概念性质(条件限制)节点的定义红黑树的结构红黑树的插入cur为红,p为红,g为黑,u存在且为红cur为红,p为红,g为黑,u不存在或u为黑,插入到p对应的一边cur为红…

Vue3报错:‘defineProps‘ is not defined no-undef

解决方法 在package.json中添加 "vue/setup-compiler-macros": true 记得在上面的 "node": true 后面加一个逗号 "eslintConfig": {"root": true,"env": {"node": true,"vue/setup-compiler-macros": t…

考PMP一定要培训吗?PMP备考可不是说着玩的

想要考项目管理认证一定要培训吗?其实这是必要的也是必须的啦,不仅仅是因为自学的难度大,个人自学很难总结学习技巧,另一个原因就是考试前还必须要有授权培训机构提供的35学时培训证明,没有这个培训证明也就直接意味着…

【C++11】initializer_list | 右值引用 | 完美转发

一切皆可列表{ }初始化 在C98,允许花括号{ } 对数组、结构体类型初始化。 class Data { public:Data(int y, int m, int d):_y(y), _m(m), _d(d){} private:int _y;int _m;int _d; };int arr[4]{0,1,2,3};//列表初始化 Data d1{2024,03,21};//列表初始化 C11允许通过{ } 初始化…

LangChain教程 | 实践过程报错集 | 持续更新

这是本人最近在做langchain教程过程中的遇到的报错,不分先后顺序。 报错:TypeError: NoneType object is not iterable 这个报错很常见,咱们要看原始报错的位置是哪里,下面是我的截图: 找到源头之后,就在源…

使用idea运行程序,发现控制台的中文出现乱码

修改UTF-8发现没有效果,寻找.idea文件夹的encodings.xml文件,将里面的UTF-8全部变成GBK.

(一)基于IDEA的JAVA基础12

一维数组 为什么使用数组: 当我们需要存储一系列数据的时候,就需要用到数组,如果不使用数组,我们就要需要一个一个的去声明变量,这样浪费内存空间,同时效率低下。 什么是数组: 数组本身就是一个变量,只…

爱普生新一代可编程振荡器系列SG-8018

频率范围: 0.67 MHz-170 MHZ 温度范围: -40C to 105C 精度: 50 ppm including aging(包括老化) 供电电压: 1.8V, 2.5V, 3.3V(1.62V-3.63V) 低功耗: 3.2 mA-8.1 mA maximum 单端输出模式: LVCMOS 可编程上升/下降时间:输出使用(OE)或待机功能(ST) 4种封装尺寸…

testng接口自动化2@Test常见参数

接下来是Test注解里的一些常用参数 1,enabled 是否执行此用例,若enabled false,则不执行此方法,若enabled true 则此方法执行,如图test1的testDemo3设置为true,testDemo2设置为false,则testDemo2没执行 2,expecte…

蓝桥杯 历届真题 时间显示【第十二届】【省赛】【C组】

资源限制 内存限制&#xff1a;256.0MB C/C时间限制&#xff1a;1.0s Java时间限制&#xff1a;3.0s Python时间限制&#xff1a;5.0s #include<bits/stdc.h> #define int long long using namespace std; const int N 1e510; int n,m,t,d; int a[2][N],b[N]; //…

Nuxt3 实战 (三):使用 release-it 自动管理版本号和生成 CHANGELOG

release-it 能做什么&#xff1f; 增加版本号并提交 Git生成变更日志&#xff08;Changelog&#xff09;并提交到 Git创建 Git 标签并推送到远程仓库发布到 npm 等软件仓库在 GitHub、GitLab 等平台创建发行版 前置知识 在看这篇文章之前&#xff0c;我们有必要了解一下 Sem…

深入了解iOS内存(WWDC 2018)笔记-内存诊断

主要记录下用于分析iOS/macOS 内存问题的笔记。 主要分析命令&#xff1a; vmmap, leaks, malloc_history 一&#xff1a;前言 有 3 种思考方式 你想看到对象的创建吗&#xff1f;你想要查看内存中引用对象或地址的内容吗&#xff1f;或者你只是想看看 一个实例有多大&#…

构建第一个ArkTS之页面和自定义组件生命周期

在开始之前&#xff0c;我们先明确自定义组件和页面的关系&#xff1a; 自定义组件&#xff1a;Component装饰的UI单元&#xff0c;可以组合多个系统组件实现UI的复用&#xff0c;可以调用组件的生命周期。页面&#xff1a;即应用的UI页面。可以由一个或者多个自定义组件组成&…

nginx到底是怎么工作的

工作流程 用户通过域名发出访问Web服务器的请求&#xff0c;该域名被DNS服务器解析为反向代理服务器的IP地址反向代理服务器接受用户的请求反向代理服务器在本地缓存中查找请求的内容&#xff0c;找到后直接把内容发送给用户如果本地缓存里没有用户所请求的信息内容&#xff0…

【Java】Java中类的初始化顺序(静态方法,静态块,非静态块,最后有流程图)

&#x1f4dd;个人主页&#xff1a;哈__ 期待您的关注 在日常使用Java的时候&#xff0c;我们都接触过new这个关键字&#xff0c;那你是否知道在我们的对象真正创建出来之前都做了哪些事情呢&#xff1f; 实际上要去判断一个类的初始化的顺序&#xff0c;需要分一下情况&…

Qt使用QWidget重绘实现圆环形渐变色进度条(支持不确定进度模式)

效果如下&#xff1a; 从纯竖直方向顶部蓝色到底部青色的渐变。 从左上角偏左45到右下角偏右45的蓝色到青色渐变。 从左上角偏左22.5到右下角偏右22.5的蓝色到青色渐变。&#xff08;这个角度渐变最好看&#xff09; 可以选择添加背景图片 支持两种模式&#xff1a;正常进度模…

媒体邀约专访如何深入的做一篇专访报道?流程分享

传媒如春雨&#xff0c;润物细无声&#xff0c;大家好&#xff0c;我是51媒体网胡老师。 进行媒体邀约专访并深入撰写一篇专访报道是一个系统性工作&#xff0c;涉及多个环节。以下是一个详细的流程分享&#xff1a; 一、前期准备 确定专访目的与主题&#xff1a;明确专访希…

Training - 使用 WandB 配置管理模型训练过程

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137529140 WandB (Weights&Biases) 是轻量级的在线模型训练可视化工具&#xff0c;类似于 TensorBoard&#xff0c;可以帮助用户跟踪…

可编程网关:如何助力智慧工厂实现智能化管理

一个具体的实际案例&#xff0c;详细说明可编程网关在某汽车零部件智慧工厂中的应用细节&#xff1a; 案例背景&#xff1a; 某大型汽车零部件制造企业&#xff0c;致力于提升生产效率、降低运营成本、确保产品质量&#xff0c;决定对其传统工厂进行全面数字化改造&#xff0…