一、进程池
1、进程池 ProcessPoolExecutor
优点:
-
减少进程创建和销毁的开销: 创建和销毁进程是一个相对耗时的操作,涉及到操作系统的系统调用和资源分配。使用进程池,可以预先创建一组进程,并在需要时重用这些进程,避免了频繁的进程创建和销毁开销,提高了程序的性能和效率。
-
控制并发进程数量: 进程池允许你限制并发执行的进程数量,避免进程数量过多导致系统资源耗尽。通过设置进程池的大小,可以根据系统的处理能力和任务的特性来合理分配进程资源,确保进程的数量在适当范围内。
-
充分利用多核 CPU: 进程池可以充分利用多核 CPU 的优势,通过并行执行多个进程,提高了系统的处理能力和任务的执行效率。每个进程都拥有自己的 Python 解释器和 GIL(对于 CPython 解释器),实现了真正的并行执行,适用于 CPU 密集型任务。
-
避免进程资源竞争和冲突: 在多进程编程中,如果进程数量过多,可能会导致进程之间的资源竞争和冲突,如共享内存的同步问题等。使用进程池可以限制进程数量,避免过多的进程之间产生资源竞争和冲突,提高了程序的稳定性和可靠性。
-
提供任务队列和排队机制: 进程池通常与任务队列结合使用,可以将任务放入队列中,进程池会自动从队列中取出任务进行处理。这样可以避免任务丢失和重复执行的问题,并提供了一种简单而高效的任务调度和排队机制,使任务的处理更加有序和可控。
总的来说,进程池通过管理和重用进程、限制并发数量、提高任务调度效率等方式,优化了多进程编程的性能和资源利用,提供了一种可靠且高效的进程管理机制。在需要处理大量并发任务、充分利用多核 CPU 或涉及到共享资源的场景下,使用进程池可以有效地提升系统的性能和可扩展性。
2、使用进程池执行task任务,调用回调函数
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor def task(n, m): return n + m # 1+2 def task1(): # ... return { 'username': 'kevin', 'password': 123 } def callback(res): print(res.result()) # 固定写法 def callback1(res): print(res.result()) print(res.result()['username']) if __name__ == '__main__': """开一个进程池,提前放进去多个进程""" # max_workers 参数设置存放的进程数 pool = ProcessPoolExecutor(2) # 池子里面有2个工作者,其实就是实例化了5个进程 # 有了进程池,我们现在往池子里面丢任务 # pool.submit(task, 1, 2) # 进程池要主动调一个回调函数,来把结果给到我们,回调函数需要我们自己提前写好 pool.submit(task, n=1, m=2).add_done_callback(callback) pool.submit(task1).add_done_callback(callback1) pool.shutdown() # join + close print(123)
二、线程池
1、线程池是一种用于管理和重用线程的技术,优点如下:
-
减少线程创建和销毁的开销: 创建和销毁线程是一个相对昂贵的操作,涉及到操作系统的系统调用和资源分配。使用线程池,可以预先创建一组线程,并在需要时重用这些线程,避免了频繁的线程创建和销毁开销,提高了程序的性能和效率。
-
控制并发线程数量: 线程池允许你限制并发执行的线程数量,避免线程数量过多导致系统资源耗尽。通过设置线程池的大小,可以根据系统的处理能力和任务的特性来合理分配线程资源,确保线程的数量在适当范围内。
-
提高响应性和吞吐量: 线程池可以有效地管理和调度线程,通过并发执行多个任务,提高了系统的响应性和任务的处理吞吐量。当一个任务完成后,线程池可以立即将下一个任务分配给空闲的线程,减少了任务等待时间,提高了任务的执行效率。
-
避免线程资源竞争和冲突: 在多线程编程中,如果线程数量过多,可能会导致线程之间的资源竞争和冲突,如锁竞争、死锁等问题。使用线程池可以限制线程数量,避免过多的线程之间产生资源竞争和冲突,提高了程序的稳定性和可靠性。
-
提供任务队列和排队机制: 线程池通常与任务队列结合使用,可以将任务放入队列中,线程池会自动从队列中取出任务进行处理。这样可以避免任务丢失和重复执行的问题,并提供了一种简单而高效的任务调度和排队机制,使任务的处理更加有序和可控。
总的来说,线程池通过管理和重用线程、限制并发数量、提高任务调度效率等方式,优化了多线程编程的性能和资源利用,提供了一种可靠且高效的线程管理机制。在需要处理大量并发任务的场景下,使用线程池可以有效地提升系统的性能和可扩展性。
2、线程池 ThreadPoolExecutor
from concurrent.futures import ThreadPoolExecutor def task(n, m): return n + m # 1+2 def task1(): # ... return { 'username': 'kevin', 'password': 123 } def callback(res): print(res.result()) # 固定写法 def callback1(res): print(res.result()) print(res.result()['username']) if __name__ == '__main__': """开一个进程池,提前放进去多个进程""" # max_workers 参数设置存放的进程数 pool = ThreadPoolExecutor(2) # 池子里面有2个工作者,其实就是实例化了2个线程 # 有了进程池,我们现在往池子里面丢任务 # pool.submit(task, 1, 2) # 进程池要主动调一个回调函数,来把结果给到我们,回调函数需要我们自己提前写好 pool.submit(task, n=1, m=2).add_done_callback(callback) pool.submit(task1).add_done_callback(callback1) pool.shutdown() # join + close print(123)
3、使用线程池爬取网页
这里爬取到的网页内容是二进制的,所以写入用wb模式
import requests from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor def get_page(url): res = requests.get(url) # 爬取网页 name = url.rsplit('/')[-1] + '.html' return {'name': name, 'text': res.content} # 使用回调函数时会返回一个 concurrent.futures.Future 对象, # 它代表了异步操作的结果。res.result() 是 Future 对象的方法,用于获取异步操作的结果. def call_back(res): print(res.result()['name']) # 回调函数获取的是task的返回值,这里get_page返回值是一个字典 with open(res.result()['name'], 'wb') as f: f.write(res.result()['text']) if __name__ == '__main__': pool = ThreadPoolExecutor(2) urls = ['http://www.baidu.com', 'http://www.cnblogs.com', 'http://www.taobao.com'] for url in urls: pool.submit(get_page, url).add_done_callback(call_back) # www.baidu.com.html # www.cnblogs.com.html # www.taobao.com.html
三、进程池的两种实现机制
multiprocessing.Pool
和 concurrent.futures.ProcessPoolExecutor
1、multiprocessing.Pool
的优点和适用场景:
- 优点:
- 使用Python标准库中的
multiprocessing
模块,易于使用和理解。 - 提供了方便的方法,如
apply_async
用于异步提交任务,close
和join
用于管理进程池。 - 可以根据并发需求灵活设置进程池的大小。
- 使用Python标准库中的
- 缺点:
- 在某些情况下,由于进程间通信的开销和资源消耗,使用多进程可能会导致更高的系统开销。
- 在Windows系统中,
multiprocessing.Pool
使用的是fork
方式,而不是spawn
方式,可能会受到一些限制。
- 适用场景:
- 一般的并发连接处理。
- 对于计算密集型任务,其中存在大量CPU计算,并且不涉及过多的进程间通信。
2、concurrent.futures.ProcessPoolExecutor
的优点和适用场景:
- 优点:
- 基于
concurrent.futures
模块,提供了高级的异步执行和进程池的接口。 - 可以更方便地使用上下文管理器(
with
语句)来管理进程池的生命周期。 - 具有与
ThreadPoolExecutor
相同的接口,使得在需要切换到线程池的情况下更加方便。
- 基于
- 缺点:
- 在某些情况下,由于进程间通信的开销和资源消耗,使用多进程可能会导致更高的系统开销。
- 在Windows系统中,仍然受到一些限制,因为它仍然使用的是
multiprocessing.Pool
。
- 适用场景:
- 对于需要更高级和灵活接口的进程池需求。
- 可以与
concurrent.futures.ThreadPoolExecutor
一起使用,在不同场景之间进行切换。
四、结合socket编程实现并发接收客户端
服务端方式一: ProcessPoolExecutor
import socket from concurrent.futures import ProcessPoolExecutor def handle_client(sock): while True: try: data = sock.recv(1024) print('客户端发来的消息是:%s' % data) if len(data) == 0: break sock.send(b'this is server!') except ConnectionResetError as e: print(e) break sock.close() if __name__ == '__main__': server = socket.socket() server.bind(('127.0.0.1', 8001)) server.listen(4) with ProcessPoolExecutor(5) as executor: # 创建进程池,最大进程数为5 while True: sock, addr = server.accept() executor.submit(handle_client, sock) # 使用进程池提交任务 server.close()
服务端方式二:
import socket import multiprocessing as mp def handle_client(sock): while True: try: data = sock.recv(1024) print('客户端发来的消息是:%s' % data) if len(data) == 0: break sock.send(b'this is server!') except ConnectionResetError as e: print(e) break sock.close() if __name__ == '__main__': server = socket.socket() server.bind(('127.0.0.1', 8001)) server.listen(4) pool = mp.Pool(processes=5) # 创建进程池,指定进程数为5 while True: sock, addr = server.accept() pool.apply_async(handle_client, args=(sock,)) # 使用进程池异步提交任务 pool.close() pool.join() server.close()
客户端
import socket # 1. 实例化对象 client = socket.socket() # 默认的参数就是基于网络的tcp socket # 2. 连接服务端 client.connect(('127.0.0.1', 8001)) while True: res = input('请输入你要发送的消息:') if res == 'exit': break if res == '': continue # 3. 给服务端发送消息 client.send(res.encode('utf8')) # 4. 接受服务端发来的消息 data = client.recv(1024) print('服务端发来的消息:%s' % data) # 5. 断开连接 client.close()
标签:__,res,任务,线程,进程,pool From: https://www.cnblogs.com/dgp-zjz/p/17536048.html