并发编程之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 @ 2024-03-03 11:32  Xiao0101  阅读(245)  评论(0)    收藏  举报