936 字
5 分钟
第17章 使用future处理并发
第17章 使用future处理并发
[toc]
futures模块
Python3引入的concurrent.futures模块。concurrent.futures 是python3新增加的一个库,用于并发处理,提供了多线程和多进程的并发功能 类似于其他语言里的线程池(也有一个进程池),他属于上层的封装,对于用户来说,不用在考虑那么多东西了
concurrent提供了两种并发模型,一个是多线程ThreadPoolExecutor,一个是多进程ProcessPoolExecutor。对于IO密集型任务宜使用多线程模型。对于计算密集型任务应该使用多进程模型。
多线程模式ThreadPoolExecutor
多线程模式适合IO密集型运算,这里我要使用sleep来模拟一下慢速的IO任务 这里使用Google fire开源库来简化命令行参数处理
import timeimport fireimport threadingfrom concurrent.futures import ThreadPoolExecutor, wait
# 分割子任务def each_task(index): time.sleep(1) # 睡1s,模拟IO print("thread %d square %d" % (threading.current_thread().ident, index)) return index * index # 返回结果
def run(thread_num, task_num): # 实例化线程池,thread_num个线程 executor = ThreadPoolExecutor(thread_num) start = time.time()
fs = [] # future列表 for i in range(task_num): fs.append(executor.submit(each_task, i)) # 提交任务
wait(fs) # 等待计算结束 end = time.time() duration = end - start
s = sum([f.result() for f in fs]) # 求和 print("total result=%d cost: %s" % (s, duration)) executor.shutdown() # 销毁线程池
if __name__ == '__main__': fire.Fire(run)执行返回结果:
python 1.py 2 10 # 也就是2个线程跑10个任务thread 5216 square 0thread 5856 square 1thread 5216 square 2thread 5856 square 3thread 5216 square 4thread 5856 square 5thread 5216 square 6thread 5856 square 7thread 5856 square 9thread 5216 square 8total result=285 cost: 5.06045389175415为什么输出乱了,这是因为print操作不是原子的,它是两个连续的write操作合成的,第一个write输出内容,第二个write输出换行符,write操作本身是原子的,但是在多线程环境下,这两个write操作会交错执行,所以输出就不整齐了。如果将代码稍作修改,将print改成单个write操作,输出就整齐了 ---发现改了还是不行
调大参数看看效果:
python 1.py 10 10thread 4792 square 9thread 10380 square 6thread 14420 square 7thread 5720 square 3thread 13808 square 4thread 17344 square 1thread 9172 square 5thread 10684 square 8thread 11696 square 0thread 6300 square 2total result=285 cost: 1.05可以看到1s中就完成了所有的任务。这就是多线程的魅力,可以将多个IO操作并行化,减少整体处理时间。
多进程模式ProcessPoolExecutor
相比多线程适合处理IO密集型任务,多进程适合计算密集型。
import osimport sysimport mathimport timeimport firefrom concurrent.futures import ProcessPoolExecutor, wait
# 分割子任务def each_task(n): # 按公式计算圆周率 s = 0.0 for i in range(n): s += 1.0 / (i + 1) / (i + 1) pi = math.sqrt(6 * s) # os.getpid可以获得子进程号 sys.stdout.write("process %s n=%d pi=%s\n" % (os.getpid(), n, pi)) return pi
def run(process_num, *ns): # 输入多个n值,分成多个子任务来计算结果 # 实例化进程池,process_num个进程 executor = ProcessPoolExecutor(process_num) start = time.time() fs = [] # future列表 for n in ns: fs.append(executor.submit(each_task, int(n))) # 提交任务 wait(fs) # 等待计算结束 end = time.time() duration = end - start print("total cost: %.2f" % duration) executor.shutdown() # 销毁进程池
if __name__ == '__main__': fire.Fire(run)执行返回结果:
python 1.py 1 5000000 5001000 5002000 5003000process 10024 n=5000000 pi=3.141592462603821process 10024 n=5001000 pi=3.141592462641988process 10024 n=5002000 pi=3.1415924626801544process 10024 n=5003000 pi=3.141592462718321total cost: 4.62增大一个进程看看效果:
python 1.py 2 5000000 5001000 5002000 5003000process 5860 n=5000000 pi=3.141592462603821process 14948 n=5001000 pi=3.141592462641988process 14948 n=5003000 pi=3.141592462718321process 5860 n=5002000 pi=3.1415924626801544total cost: 2.78从耗时上看缩短了接近1半,说明多进程确实起到了计算并行化的效果
深入原理
第17章 使用future处理并发
https://blog.zhequtao.com/posts/python/流畅的python/第17章使用future处理并发/