本文共 3562 字,大约阅读时间需要 11 分钟。
进程池与线程池是Python中实现并发执行的两种主要方式。并发执行可以让多个任务看起来同时运行,核心原理是切换+保存状态。需要注意的是,操作系统无法无限创建进程或线程,特别是当任务数量超过CPU核数时,若不合理配置进程池或线程池,反而可能降低效率。
进程池和线程池的主要作用是限制任务的并发执行数量。ProcessPoolExecutor用于进程池,默认最多创建4个进程;ThreadPoolExecutor用于线程池,默认最多创建CPU核数的5倍。两者的异步调用接口都支持submit任务和map操作,后者可代替for循环,代码更加简洁。
需要区分同步调用和异步调用:同步调用会阻塞当前线程,等待任务完成后才继续执行;异步调用则立即返回,任务并发执行。进程池和线程池的主要区别在于它们使用的资源类型:进程池创建进程,线程池创建线程。
以下是代码示例:
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutorimport time, random, osdef get(url): print('%s GET %s' % os.getpid(), url) time.sleep(random.randint(1, 3)) response = requests.get(url) return response.text if response.status_code == 200 else '下载失败'def parse(future): time.sleep(1) result = future.result() print('%s 解析结果为 %s' % os.getpid(), len(result))if __name__ == '__main__': urls = [ 'https://www.baidu.com', 'https://www.sina.com.cn', 'https://www.tmall.com', 'https://www.jd.com', 'https://www.python.org', 'https://www.openstack.org', 'https://www.baidu.com', 'https://www.baidu.com', 'https://www.baidu.com', ] p = ProcessPoolExecutor(9) start = time.time() for url in urls: future = p.submit(get, url) future.add_done_callback(parse) p.shutdown(wait=True) print('主', time.time() - start) print('主', os.getpid()) 线程池和进程池在执行方式上相同,但线程池更适合I/O密集型任务,而进程池则适合计算密集型任务。map函数可以替代submit操作,简化代码。
以下是基于yield实现的协程示例:
import timedef func1(): for i in range(10000000): i + 1def func2(): for i in range(10000000): i + 1start = time.time()func1()func2()stop = time.time()print(stop - start) # 1.675490379333496 秒
基于yield的协程实现:
import timedef func1(): while True: print('func1') time.sleep(5) yielddef func2(): g = func1() for i in range(10000000): print('func2') time.sleep(100) next(g)start = time.time()func2()stop = time.time()print(stop - start) # 26.000497秒 yield函数返回生成器对象,适用于单线程并发。
gevent是第三方库,通过绿色线程实现协程。其核心思想是自动切换协程执行,减少等待时间。需要在启动时导入monkey patch:
from gevent import monkey; monkey.patch_all()from gevent import spawn, joinallimport timedef play(name): print('%s play 1' % name) time.sleep(5) print('%s play 2' % name)def eat(name): print('%s eat 1' % name) time.sleep(3) print('%s eat 2' % name)start = time.time()g1 = spawn(play, 'lxx')g2 = spawn(eat, 'lxx')joinall([g1, g2])print('主', time.time() - start) gevent支持socket并发:
from gevent import monkey; monkey.patch_all()from socket import *from gevent import spawndef communicate(conn): while True: try: data = conn.recv(1024) if len(data) == 0: break conn.send(data.upper()) except ConnectionResetError: break conn.close()def server(ip, port, backlog=5): server = socket(AF_INET, SOCK_STREAM) server.bind((ip, port)) server.listen(backlog) while True: conn, client_addr = server.accept() spawn(communicate, conn)if __name__ == '__main__': g1 = spawn(server, '127.0.0.1', 8080) g1.join()
线程池实现socket并发:
from threading import Thread, current_threadfrom socket import *def client(): conn = socket(AF_INET, SOCK_STREAM) conn.connect(('127.0.0.1', 8080)) n = 0 while True: msg = f'{current_thread().name} say hello {n}' n += 1 conn.send(msg.encode('utf-8')) data = conn.recv(1024) print(data.decode('utf-8'))if __name__ == '__main__': for _ in range(500): t = Thread(target=client) t.start() 协程与线程相比,能够更高效地利用CPU资源,特别是在I/O密集型任务中表现优异。
转载地址:http://uklfk.baihongyu.com/