并发编程之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)
posted @   Xiao0101  阅读(204)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
点击右上角即可分享
微信分享提示

目录