并发编程之Python标准模块--concurrent
引言
首先,需要注意一下:不能无限的开进程,不能无限的开线程,最常用的就是开进程池,开线程池。
其中,回调函数非常重要,回调函数其实可以作为一种编程思想,谁好了谁就去掉只要你用并发,就会有锁的问题,但是你不能一直去自己加锁吧。
那么我们就用QUEUE,这样还解决了自动加锁的问题,但是由Queue延伸出的一个点也非常重要的概念。以后写程序也会用到这个思想。就是生产者与消费者问题。
Python标准模块--concurrent.futures(并发未来)
concurent.future模块介绍
-
concurent.future模块是用来创建并行的任务,提供了更高级别的接口,为了异步执行调用
-
concurent.future这个模块用起来非常方便,它的接口也封装的非常简单
-
concurent.future模块既可以实现进程池,也可以实现线程池
-
模块导入进程池和线程池
from concurrent.futures import ProcessPoolExecutor,ThreadPoolExecutor
还可以导入一个Executor,但是你别这样导,这个类是一个抽象类
抽象类的目的是规范他的子类必须有某种方法(并且抽象类的方法必须实现),但是抽象类不能被实例化
-
p = ProcessPoolExecutor(max_works)对于进程池如果不写max_works:默认的是cpu的数目,默认是4个。
p = ThreadPoolExecutor(max_works)对于线程池如果不写max_works:默认的是cpu的数目*5。
-
如果是进程池,得到的结果如果是一个对象。我们得用一个.get()方法得到结果
但是现在用了concurent.future模块,我们可以用obj.result方法
p.submit(task,i) #相当于apply_async异步方法
p.shutdown() #默认有个参数wite=True (相当于pool.close()+pool.join())
进程池、线程池使用案例
from concurrent.futures import ProcessPoolExecutor # 进程池模块 from concurrent.futures import ThreadPoolExecutor # 线程池模块 import os, time, random # 下面是以进程池为例, 线程池只是模块改一下即可 def talk(name): print('name: %s pis%s run' % (name, os.getpid())) time.sleep(random.randint(1, 3)) if __name__ == '__main__': pool = ProcessPoolExecutor(4) # 设置线程池大小,默认等于cpu核数 for i in range(10): pool.submit(talk, '进程%s' % i) # 异步提交(只是提交需要运行的线程不等待) # 作用1:关闭进程池入口不能再提交了 作用2:相当于jion 等待进程池全部运行完毕 pool.shutdown(wait=True) print('主进程') # name: 进程0 pis33564 run # name: 进程1 pis31212 run # name: 进程2 pis22096 run # name: 进程3 pis31176 run # name: 进程4 pis22096 run # name: 进程5 pis22096 run # name: 进程6 pis31176 run # name: 进程7 pis33564 run # name: 进程8 pis31212 run # name: 进程9 pis31176 run # 主进程
任务的提交方式
- 同步调用:提交完任务后,就在原地等待任务执行完毕,拿到结果,再执行下一行代码,程序是串行执行
- 异步调用:提交任务之后不等待任务的返回结果,继续执行代码
concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecutor: 进程池,提供异步调用
同步调用
from concurrent.futures import ProcessPoolExecutor # 进程池模块 import os, time, random # 1、同步调用: 提交完任务后、就原地等待任务执行完毕,拿到结果,再执行下一行代码(导致程序串行执行) def talk(name): print('name: %s pis%s run' % (name, os.getpid())) time.sleep(random.randint(1, 3)) if __name__ == '__main__': pool = ProcessPoolExecutor(4) for i in range(10): pool.submit(talk, '进程%s' % i).result() # 同步调用,result(),相当于join 串行 pool.shutdown(wait=True) print('主进程')
异步调用
from concurrent.futures import ProcessPoolExecutor # 进程池模块 import os, time, random def talk(name): print('name: %s pis%s run' % (name,os.getpid())) time.sleep(random.randint(1, 3)) if __name__ == '__main__': pool = ProcessPoolExecutor(4) for i in range(10): pool.submit(talk, '进程%s' % i) # 异步调用,不需要等待 pool.shutdown(wait=True) print('主进程')
应用线程池(下载网页并解析)
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor import requests import time, os def get_page(url): print('<%s> is getting [%s]' % (os.getpid(), url)) response = requests.get(url) if response.status_code == 200: # 200代表状态:下载成功了 return {'url': url, 'text': response.text} def parse_page(res): res = res.result() print('<%s> is getting [%s]' % (os.getpid(), res['url'])) with open('db.txt', 'a') as f: parse_res = 'url:%s size:%s\n' % (res['url'], len(res['text'])) f.write(parse_res) if __name__ == '__main__': # p = ThreadPoolExecutor() p = ProcessPoolExecutor() l = [ 'http://www.baidu.com', 'http://www.baidu.com', 'http://www.baidu.com', 'http://www.baidu.com', ] for url in l: res = p.submit(get_page, url).add_done_callback(parse_page) # 这里的回调函数拿到的是一个对象。得 # 先把返回的res得到一个结果。即在前面加上一个res.result() #谁好了谁去掉回调函数 # 回调函数也是一种编程思想。不仅开线程池用,开线程池也用 p.shutdown() # 相当于进程池里的close和join print('主', os.getpid())
map函数的应用
# map函数举例 obj= map(lambda x:x**2 ,range(10)) print(list(obj)) #运行结果[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]
可以和上面的开进程池/线程池的对比着看,就能发现map函数的强大了
# 我们的那个p.submit(task,i)和map函数的原理类似。我们就 # 可以用map函数去代替。更减缩了代码 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor import os, time, random def task(n): print('[%s] is running' % os.getpid()) time.sleep(random.randint(1, 3)) # I/O密集型的,,一般用线程,用了进程耗时长 return n ** 2 if __name__ == '__main__': p = ProcessPoolExecutor() obj = p.map(task, range(10)) p.shutdown() # 相当于close和join方法 print('=' * 30) print(obj) # 返回的是一个迭代器 print(list(obj))
回调机制
可以为进程池或线程池内的每个进程或线程绑定一个函数,该函数在进程或线程的任务执行完毕后自动触发,并接收任务的返回值当作参数,该函数称为回调函数。
#parse_page拿到的是一个future对象obj,需要用obj.result()拿到结果 p.submit(这里异步调用).add_done_callback(方法)
- 案例:下载解析网页页面
import time import requests from concurrent.futures import ThreadPoolExecutor # 线程池模块 def get(url): print('GET %s' % url) response = requests.get(url) # 下载页面 time.sleep(3) # 模拟网络延时 return {'url': url, 'content': response.text} # 页面地址和页面内容 def parse(res): res = res.result() # !取到res结果 【回调函数】带参数需要这样 print('%s res is %s' % (res['url'], len(res['content']))) if __name__ == '__main__': urls = { 'http://www.baidu.com', 'http://www.360.com', 'http://www.iqiyi.com' } pool = ThreadPoolExecutor(2) for i in urls: pool.submit(get, i).add_done_callback(parse) # 【回调函数】执行完线程后,跟一个函数
模块介绍
concurrent.futures模块提供了高度封装的异步调用接口 ThreadPoolExecutor:线程池,提供异步调用 ProcessPoolExecutor: 进程池,提供异步调用 Both implement the same interface, which is defined by the abstract Executor class.
基本方法
1. submit(fn, *args, **kwargs) 异步提交任务 2. map(func, *iterables, timeout=None, chunksize=1) 取代for循环submit的操作 3. shutdown(wait=True) 相当于进程池的pool.close()+pool.join()操作 wait=True,等待池内所有任务执行完毕回收完资源后才继续 wait=False,立即返回,并不会等待池内的任务执行完毕 但不管wait参数为何值,整个程序都会等到所有任务执行完毕 submit和map必须在shutdown之前 4. result(timeout=None) 取得结果 5. add_done_callback(fn) 回调函数
使用方法示例
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor import requests # 给线程池设置最大工作的线程数 pool = ThreadPoolExecutor(10) urls = [ 'http://cms-bucket.nosdn.127.net/2018/10/16/10e36050547445f6b8972daf7373a222.jpeg', 'http://kwcdn.000dn.com/swfs/59/39972xmyj0206/pm.jpg', 'http://pic-bucket.nosdn.127.net/photo/0008/2018-10-14/DU48KHUS2FKJ0008NOS.jpg', 'http://cms-bucket.nosdn.127.net/2018/10/16/b3a3fab2d65a41b79e0764727ae6d179.jpeg', 'http://cms-bucket.nosdn.127.net/2018/10/15/92cbe61fc5ec40ab94f5d2f0ed867718.jpeg', 'http://static.mx.jzyx.com/themes/v1.9/ad/v24/feature2.jpg', 'https://webinput.nie.netease.com/img/hy/icon.png', 'https://nie.res.netease.com/r/pic/20180807/3c70afb1-074e-453c-8f57-dfd1b6087fbf.png', 'https://nie.res.netease.com/r/pic/20181016/6b26e033-02fe-4c21-a10c-7eb04a24a612.jpg', 'http://img0.imgtn.bdimg.com/it/u=19453856,4281427172&fm=26&gp=0.jpg', ] def task(url): response = requests.get(url) return response # response会传递到call_back的参数中 def call_back(response): # 拿到的是一个future的对象 # 从对象中取出task中返回的结果 response.result() # 对回调过来的信息进行解析 pass for url in urls: # 使用回调函数能够用返回的结果实时的去解析, 实现异步非阻塞 pool.submit(task, url).add_done_callback(call_back) # 如果使用shutdown会等待所有任务执行结束后再去执行主线程中的代码 # pool.shutdown(True)
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!