并发编程

并发编程

1.操作系统认识

  • 操作系统种类

    • 多道操作系统
      • 在一个任务遇到IO时让出CPU
        • 切换由操作系统完成,也需要耗时
      • 特点
        • 数据隔离
        • 时空复用
    • 分时操作系统
      • 给时间分片,让多个任务轮流使用CPU
      • 时间分片
        • CPU轮转
        • 每一个程序分配一个时间片
        • 切换要占用时间,反而降低了CPU的利用率,但是提高了用户体验
    • 实时操作系统
    • 网络操作系统
    • 分布式操作系统
  • 目前常用的操作系统

    • 分时操作系统 + 多道操作系统 + 实时操作系统
      • 运行机制:多个程序一起在计算机中执行,一个程序如果遇到IO操作,切出去让出CPU,一个程序没有遇到IO,但是时间片到时了,切出去让出CPU
  • 操作系统调度进程的算法

    • 短作业优先
    • 先来先服务
    • 时间片轮转
    • 多级反馈算法

2.进程

2.1进程相关概念

  • 进程的特点

    • 开销大,数据隔离,最小资源分配单位,cpython下可以利用多核
  • 进程的三状态

    • 就绪
    • 运行
    • 阻塞
  • 操作系统创建进程的方式不同

    • windows操作系统执行开启进程的代码
      • 实际上新的子进程需要通过import父进程的代码来完成数据的导入工作,所以有一些内容我们只希望在父进程中完成,就写在if __name__ == '__main__':下面
    • ios linux操作系统创建进程 fork
      • 只copy代码而不执行,只执行要start的内容
  • 主进程和子进程之间的关系

    • 主进程的代码结束——>所有的子进程结束——>给子进程回收资源——>主进程结束

2.2进程开启(multiprocession)

  • from multiprocessing import Process

2.2.1面向函数开启

  • 创建进程:使用Process类

  • 基本模板

    • 创建进程对象p = Process(target=函数名, args=(参数1,))
    • 开启进程p.start()
      • 给了操作系统一条指令,并不是立即开启,需要响应时间

2.2.2面向对象开启进程

import os
import time
from multiprocessing import Process

class MyProcecss2(Process):
    def run(self):
        while True:
            print('is alive')
            time.sleep(0.5)

class MyProcecss1(Process):
    def __init__(self,x,y):
        self.x = x
        self.y = y
        super().__init__()
    def run(self):
        print(self.x,self.y,os.getpid())
        for i in range(5):
            print('in son2')
            time.sleep(1)

if __name__ == '__main__':
    mp = MyProcecss1(1,2)
    mp.daemon = True
    mp.start()
    print(mp.is_alive())
    mp.terminate()
    # mp2 = MyProcecss2()
    # mp2.start()
    # print('main :',os.getpid())
    # time.sleep(1)
  • 注意点
    • 一个类只能创建一个进程,如果想创建多个进程,那么构建多个类
    • 要想传入参数,需要重写__init__方法,但重写的时候必须要保留原本的__init__中的参数,使用super().__init__()

2.3守护进程

  • p.daemon = True通过该方法将进程设置为守护进程
  • 特点
    • 守护进程是随着主进程的代码结束而结束的
    • 所有子进程都必须在主进程结束之前结束,由主进程来负责回收资源
import time
from multiprocessing import Process

def son1(a,b):
    while True:
        print('is alive')
        time.sleep(0.5)

def son2():
    for i in range(5):
        print('in son2')
        time.sleep(1)

if __name__ == '__main__':
    p = Process(target=son1,args=(1,2))
    p.daemon = True
    p.start()      # 把p子进程设置成了一个守护进程
    p2 = Process(target=son2)
    p2.start()
    time.sleep(2)

2.4进程相关方法补充

  • os模块中,获取进程id

    • os.getpid()

      def func():
          print('start',os.getpid())
          time.sleep(1)
          print('end',os.getpid())
      
  • Process类中

    • join():阻塞直到子进程结束
    import time
    import random
    from multiprocessing import Process
    def send_mail(a):
        time.sleep(random.random())
        print('发送了一封邮件',a)
    
    if __name__ == '__main__':
        l = []	
        for i in range(10):
            p = Process(target=send_mail,args=(i,))
            p.start()
            l.append(p)
        print(l)
        for p in l:p.join()
        # 阻塞 直到上面的十个进程都结束
        print('5000封邮件已发送完毕')
    
    • p.is_alive() 判断进程是否还在运行
    • p.terminate() 结束进程,异步非阻塞的
    • p.start()p.terminate() 方法都是异步非阻塞的

2.5进程之间的数据共享

2.5.1Manage

from multiprocessing import Manager,Process,Lock

def func(dic,lock):
    with lock:
        dic['count'] -= 1

if __name__ == '__main__':
    # m = Manager()
    with Manager() as m:
        l = Lock()
        dic = m.dict({'count':100})
        p_l = []
        for i in range(100):
            p = Process(target=func,args=(dic,l))
            p.start()
            p_l.append(p)
        for p in p_l:p.join()
        print(dic)
  • 注意点:加锁
    • 封装了所有和进程相关的数据共享、数据传递相关的数据类型,但是对于 字典 列表这一类的数据操作的时候会产生数据不安全,需要加锁解决问题,并且需要尽量少的使用这种方式

2.5.2IPC机制

  • ipc机制 :队列 管道
  • 第三方工具(软件)提供给我们的IPC机制
    • redis
    • memcache
    • kafka
    • rabbitmq
    • 特点
      • 并发需求
      • 高可用
      • 断电保存数据
      • 解耦

3.线程

3.1线程相关概念

  • 线程的特点
    • 开销小,数据共享,cpu调度单位,cpython下不能利用多核

3.2线程开启(threading)

  • from threading import Thread

3.2.1面向函数开启

import time
from threading import Thread

def func(i):
    print('start son thread',i)
    time.sleep(1)
    print('end son thread',i,os.getpid())

for i in range(10):
    Thread(target=func,args=(i,)).start()
print('main')

3.2.2面向对象开启

import time
from threading import Thread

class MyThread(Thread):
    def __init__(self,i):
        self.i = i
        super().__init__()
    def run(self):
        print('start',self.i,self.ident)
        time.sleep(1)
        print('end',self.i)

for i in range(10):
    t = MyThread(i)
    t.start()
    print(t.ident)

3.3守护线程

import time
from threading import Thread
def son1():
    while True:
        time.sleep(0.5)
        print('in son1')
def son2():
    for i in range(5):
        time.sleep(1)
        print('in son2')
t =Thread(target=son1)
t.daemon = True
t.start()
Thread(target=son2).start()
time.sleep(3)
  • 注意点
    • 守护线程一直等到所有的非守护线程都结束之后才结束,除了守护了主线程的代码之外也会守护子线程

3.4线程相关方法补充

  • join方法:阻塞直到子线程执行结束

    import time
    from threading import Thread
    
    def func(i):
        print('start son thread',i)
        time.sleep(1)
        print('end son thread',i,os.getpid())
    t_l = []
    for i in range(10):
        t = Thread(target=func,args=(i,))
        t.start()
        t_l.append(t)
    for t in t_l:t.join()
    print('子线程执行完毕')
    
  • current_thread类,表示当前线程

  • ident方法,线程id号

  • enumerate类,表示有几个线程开启

  • active_count类,开启线程的个数

from threading import current_thread,enumerate,active_count
def func(i):
    t = current_thread()
    print('start son thread',i,t.ident)
    time.sleep(1)
    print('end son thread',i,os.getpid())

t = Thread(target=func,args=(1,))
t.start()
print(t.ident)
print(current_thread().ident)   # 水性杨花 在哪一个线程里,current_thread()得到的就是这个当前线程的信息
print(enumerate())
print(active_count())   # =====len(enumerate())

3.5GIL全局解释器锁

  • cpython解释器不能实现多线程利用多核
  • 锁:GIL全局解释器锁
    • 保证了整个python程序中,只能有一个线程被CPU执行
    • 原因:cpython解释器中特殊的垃圾回收机制
    • GIL锁导致了线程不能并行,可以并发
  • 所以使用多线程并不影响高io型的操作,只会对高计算型的程序由效率上的影响
  • 遇到高计算
    • 多进程 + 多线程
    • 分布式

4.生产者消费者模型

4.1使用队列

  • from multiprocessing import Process,Queue

  • 解耦:修改方便,复用度高,可读性好

    • 把写在一起的大的功能分开成多个小的功能处理
  • 进程

    • 一个进程就是一个生产者
    • 一个进程就是一个消费者
  • 队列

    • 生产者和消费者之间的容器就是队列
  • 代码

    import time
    import random
    from multiprocessing import Process,Queue
    
    def producer(q,name,food):
        for i in range(10):
            time.sleep(random.random())
            fd = '%s%s'%(food,i)
            q.put(fd)
            print('%s生产了一个%s'%(name,food))
    
    def consumer(q,name):
        while True:
            food = q.get()
            if not food:break
            time.sleep(random.randint(1,3))
            print('%s吃了%s'%(name,food))
    
    
    def cp(c_count,p_count):
        q = Queue(10)
        for i in range(c_count):
            Process(target=consumer, args=(q, 'alex')).start()
        p_l = []
        for i in range(p_count):
            p1 = Process(target=producer, args=(q, 'wusir', '泔水'))
            p1.start()
            p_l.append(p1)
        for p in p_l:p.join()
        for i in range(c_count):
            q.put(None)
    if __name__ == '__main__':
        cp(2,3)
    

4.2JoinableQueue

import time
import random
from  multiprocessing import JoinableQueue,Process

def producer(q,name,food):
    for i in range(10):
        time.sleep(random.random())
        fd = '%s%s'%(food,i)
        q.put(fd)
        print('%s生产了一个%s'%(name,food))
    q.join()	#####

def consumer(q,name):
    while True:
        food = q.get()
        time.sleep(random.random())
        print('%s吃了%s'%(name,food))
        q.task_done()	#####

if __name__ == '__main__':
    jq = JoinableQueue()
    p =Process(target=producer,args=(jq,'wusir','泔水'))
    p.start()
    c = Process(target=consumer,args=(jq,'alex'))
    c.daemon = True
    c.start()
    p.join()

5.协程

5.1定义

  • 用户级别的,由我们自己写的python代码来控制切换的,是操作系统不可见的

5.2多线程和协程

  • 在cpython解释器下,协程和线程都不能利用多核,都是在一个CPU上轮转执行
    • 由于多线程本身就不能利用多核,所以即使开启了多个线程也只能轮流在一个CPU上执行
    • 协程如果把所有的任务的IO操作都规避掉,就剩下只需要使用CPU的操作了,就意味着协程就可以做到提高CPU利用率的效果
  • 多线程和协程
    • 线程:切换需要操作系统,开销大,操作系统不可控,给操作系统的压力大
      • 操作系统对IO操作的感知更加灵敏
    • 协程:切换需要python代码,开销小,用户操作可控,完全不会增加操作系统的压力
      • 用户级别对IO操作感知比较低

5.3协程的切换

  • 两种切换方式
    • 原生python完成,yield asyncio
    • C语言完成的python模块 greenlet gevent

5.3.1C语言完成的python模块

5.3.1.1greenlet模块

  • switch方法
# greenlet
import time
from greenlet import greenlet

def eat():
    print('wusir is eating')
    time.sleep(0.5)
    g2.switch()
    print('wusir finished eat')

def sleep():
    print('小马哥 is sleeping')
    time.sleep(0.5)
    print('小马哥 finished sleep')
    g1.switch()

g1 = greenlet(eat)
g2 = greenlet(sleep)
g1.switch()

5.3.1.2.gevent模块

  • g1 = gevent.spawn(函数名)创建一个协程任务,不会立刻执行,遇到阻塞才执行
  • g1.join()阻塞,直到协程任务g1完成为止
  • from gevent import monkey monkey.patch_all()python中的阻塞,gevent不识别,因此需要执行该方法,将python的阻塞重写为gevent能够识别的阻塞
import time
print('-->',time.sleep)
import gevent
from gevent import monkey
monkey.patch_all()
def eat():
    print('wusir is eating')
    print('in eat: ',time.sleep)
    time.sleep(1)
    print('wusir finished eat')

def sleep():
    print('小马哥 is sleeping')
    time.sleep(1)
    print('小马哥 finished sleep')

g1 = gevent.spawn(eat)  # 创造一个协程任务
g2 = gevent.spawn(sleep)  # 创造一个协程任务
g1.join()   # 阻塞 直到g1任务完成为止
g2.join()   # 阻塞 直到g1任务完成为止
  • gevent.joinall([g1,g2,g3])将多个协程任务阻塞直到所有任务都结束

    import time
    import gevent
    from gevent import monkey
    monkey.patch_all()
    def eat():
        print('wusir is eating')
        time.sleep(1)
        print('wusir finished eat')
    
    def sleep():
        print('小马哥 is sleeping')
        time.sleep(1)
        print('小马哥 finished sleep')
        
    g_l = []
    for i in range(10):
        g = gevent.spawn(eat)
        g_l.append(g)
    gevent.joinall(g_l)
    
  • g1.value取到返回值,必须等到任务执行完才有返回值

    import time
    import gevent
    from gevent import monkey
    monkey.patch_all()
    def eat():
        print('wusir is eating')
        time.sleep(1)
        print('wusir finished eat')
        return 'wusir***'
    
    def sleep():
        print('小马哥 is sleeping')
        time.sleep(1)
        print('小马哥 finished sleep')
        return '小马哥666'
    
    g1 = gevent.spawn(eat)
    g2 = gevent.spawn(sleep)
    gevent.joinall([g1,g2])
    print(g1.value)
    print(g2.value)
    

5.3.2.asyncio模块

5.3.2.1启动一个协程任务

  • 注意点

    • 必须要是协程方法async def demo():
    • 阻塞前必须有await
    • 必须创建事件循环loop = asyncio.get_event_loop()
    • 任务执行必须加括号loop.run_until_complete(demo())
  • loop = asyncio.get_event_loop()

  • loop.run_until_complete(demo())

    import asyncio
    # 起一个任务
    async def demo():   # 协程方法
        print('start')
        await asyncio.sleep(1)  # 阻塞
        print('end')
    
    loop = asyncio.get_event_loop()  # 创建一个事件循环
    loop.run_until_complete(demo())  # 把demo任务丢到事件循环中去执行
    

5.3.2.2启动多个任务

  • 启动多个任务,并且没有返回值

    • 增加一个wait对象
      • wait_obj = asyncio.wait([demo(),demo(),demo()])
    • 将wait对象丢到事件循环中去loop.run_until_complete(wait_obj)
    import asyncio
    # 启动多个任务,并且没有返回值
    async def demo():   # 协程方法
        print('start')
        await asyncio.sleep(1)  # 阻塞
        print('end')
    
    loop = asyncio.get_event_loop()  # 创建一个事件循环
    ##
    wait_obj = asyncio.wait([demo(),demo(),demo()])
    ##
    loop.run_until_complete(wait_obj)
    
  • 启动多个任务并且有返回值

  • t1 = loop.create_task(demo())创建任务对象

  • t.result()获取对应任务的返回值

    async def demo():   # 协程方法
        print('start')
        await asyncio.sleep(1)  # 阻塞
        print('end')
        return 123
    
    loop = asyncio.get_event_loop()
    ####
    t1 = loop.create_task(demo())
    t2 = loop.create_task(demo())
    tasks = [t1,t2]
    wait_obj = asyncio.wait([t1,t2])
    ###
    loop.run_until_complete(wait_obj)
    for t in tasks:
        print(t.result())
    

5.3.2.3谁先回来先取谁的结果

  • 创建main协程方法
# 谁先回来先取谁的结果
import asyncio
async def demo(i):   # 协程方法
    print('start')
    await asyncio.sleep(10-i)  # 阻塞
    print('end')
    return i,123

async def main():
    task_l = []
    for i in range(10):
        task = asyncio.ensure_future(demo(i))
        task_l.append(task)
    for ret in asyncio.as_completed(task_l):
        res = await ret
        print(res)

loop = asyncio.get_event_loop()
loop.run_until_complete(main())

6.基本概念

6.1并行和并发

  • 并行
    • 两个程序,两个CPU,每个程序分别占用一个CPU自己执行自己的
      • 看起来是同时执行,实际上在每一个时间点上都在各自执行着
  • 并发
    • 两个程序,一个CPU,每一个程序交替的在一个CPU上执行
      • 看起来在同时执行,但是实际上仍然是串行

6.2同步和异步

  • 同步
    • 正在进行某个动作,要想执行另一个操作,需要停下当前这个动作,去执行另外一个操作,执行完后再继续这个动作
  • 异步
    • 进行某个动作的同时,可以执行另外一个操作

6.3阻塞和非阻塞

  • 阻塞:CPU不工作
  • 非阻塞:CPU工作

6.4同步阻塞和同步阻塞

  • 同步阻塞

    • conn.recv()
    • socket阻塞的TCP协议
  • 同步非阻塞

    • func() 没有io操作
    • socket 非阻塞的tcp协议的时候
    • 调用函数(这个函数内部不存在io操作)

6.5异步非阻塞和异步阻塞

  • 异步非阻塞
    • 把func扔到其他任务里去执行了
    • 我本身的任务和func任务各自执行各自的 没有io操作
  • 异步阻塞

7.锁

7.1互斥锁

  • 在线程中,即便有GIL,也会出现数据不安全现象

    • 操作的是全局变量
    • 做以下操作
      • += -= *= /+ 先计算再赋值才容易出现数据不安全的问题
      • 包括 lst[0] += 1 dic['key']-=1
    a = 0
    def add_f():
        global a
        for i in range(200000):
            a += 1
    
    def sub_f():
        global a
        for i in range(200000):
            a -= 1
    
    from threading import Thread
    
    t1 = Thread(target=add_f)
    t1.start()
    t2 = Thread(target=sub_f)
    t2.start()
    t1.join()
    t2.join()
    print(a)
    
  • 查看操作系统指令

    • dis.dis(函数名)查看函数中的指令
    a = 0
    def func():
        global a
        a -= 1
    import dis
    dis.dis(func)
    
  • 解决方法:使用互斥锁

    • 加锁虽然影响了程序的执行效率,但是保证了数据的安全
    a = 0
    def add_f(lock):
        global a
        for i in range(200000):
            with lock:
                a += 1
    
    def sub_f(lock):
        global a
        for i in range(200000):
            with lock:
                a -= 1
    
    from threading import Thread,Lock
    lock = Lock()
    t1 = Thread(target=add_f,args=(lock,))
    t1.start()
    t2 = Thread(target=sub_f,args=(lock,))
    t2.start()
    t1.join()
    t2.join()
    print(a)
    
  • 注意点:互斥锁是锁中的一种:在同一个线程中,不能连续acquire多次

    from threading import Lock
    lock = Lock()
    lock.acquire()
    print('*'*20)
    lock.release()
    lock.acquire()
    print('-'*20)
    lock.release()
    

7.2死锁现象

  • 死锁现象

    • 有多把锁,一把以上
    • 多把锁交替使用
  • 解决方法:使用递归锁,将多把互斥锁变成了一把递归锁

    • 递归锁:能快速解决问题,但执行效率差
      • 递归锁也会发生死锁现象,多把锁交替使用的时候
      • 递归锁也会发生死锁现象,多把锁交替使用的时候
    • 优化代码逻辑
      • 可以使用互斥锁,解决问题
      • 执行效率相对好
      • 解决问题的效率相对低
  • 使用互斥锁解决死锁问题

    import time
    from threading import Lock,Thread
    lock = Lock()
    def eat1(name,noodle_lock,fork_lock):
        lock.acquire()
        print('%s抢到面了'%name)
        print('%s抢到叉子了' % name)
        print('%s吃了一口面'%name)
        time.sleep(0.1)
        print('%s放下叉子了' % name)
        print('%s放下面了' % name)
        lock.release()
    
    def eat2(name,noodle_lock,fork_lock):
        lock.acquire()
        print('%s抢到叉子了' % name)
        print('%s抢到面了'%name)
        print('%s吃了一口面'%name)
        time.sleep(0.1)
        print('%s放下面了' % name)
        print('%s放下叉子了' % name)
        lock.release()
    
    lst = ['alex','wusir','taibai','yuan']
    Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start()
    Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start()
    Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start()
    Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start()
    

7.3递归锁

  • 递归锁:在同一个线程中,可以连续acuqire多次不会被锁住

    import time
    from threading import Thread,Lock
    noodle_lock = Lock()
    fork_lock = Lock()
    def eat1(name,noodle_lock,fork_lock):
        noodle_lock.acquire()
        print('%s抢到面了'%name)
        fork_lock.acquire()
        print('%s抢到叉子了' % name)
        print('%s吃了一口面'%name)
        time.sleep(0.1)
        fork_lock.release()
        print('%s放下叉子了' % name)
        noodle_lock.release()
        print('%s放下面了' % name)
    
    def eat2(name,noodle_lock,fork_lock):
        fork_lock.acquire()
        print('%s抢到叉子了' % name)
        noodle_lock.acquire()
        print('%s抢到面了'%name)
        print('%s吃了一口面'%name)
        time.sleep(0.1)
        noodle_lock.release()
        print('%s放下面了' % name)
        fork_lock.release()
        print('%s放下叉子了' % name)
    
    lst = ['alex','wusir','taibai','yuan']
    Thread(target=eat1,args=(lst[0],noodle_lock,fork_lock)).start()
    Thread(target=eat2,args=(lst[1],noodle_lock,fork_lock)).start()
    Thread(target=eat1,args=(lst[2],noodle_lock,fork_lock)).start()
    Thread(target=eat2,args=(lst[3],noodle_lock,fork_lock)).start()
    

8.队列

8.1先进先出队列

  • from queue import Queue
from queue import Queue  # 先进先出队列
q = Queue(5)
q.put(0)
q.put(1)
q.put(2)
q.put(3)
q.put(4)
print('444444')

print(q.get())
print(q.get())
print(q.get())
print(q.get())
print(q.get())

8.2后进先出队列

  • from queue import LifoQueue

    from queue import LifoQueue  # 后进先出队列
    # last in first out 栈
    lfq = LifoQueue(4)
    lfq.put(1)
    lfq.put(3)
    lfq.put(2)
    print(lfq.get())
    print(lfq.get())
    print(lfq.get())
    

8.3优先级队列

  • from queue import PriorityQueue

    from queue import PriorityQueue
    pq = PriorityQueue()
    pq.put((10,'alex'))
    pq.put((6,'wusir'))
    pq.put((20,'yuan'))
    print(pq.get())
    print(pq.get())
    print(pq.get())
    

9.池

9.1定义

  • 使用concurrent.futures模块

  • 预先的开启固定个数的进程数,当任务来临的时候,直接提交给已经开好的进程,让这个进程去执行就可以了,节省了进程,线程的开启 关闭 切换都需要时间,并且减轻了操作系统调度的负担

9.2进程池

  • 开销大,一个池中的任务个数限制了我们程序的并发个数
  • 不传参
import os
import time
import random
from concurrent.futures import ProcessPoolExecutor
# submit + shutdown
def func():
    print('start',os.getpid())
    time.sleep(random.randint(1,3))
    print('end', os.getpid())
if __name__ == '__main__':
    p = ProcessPoolExecutor(5)
    for i in range(10):
        p.submit(func)
    p.shutdown()   # 关闭池之后就不能继续提交任务,并且会阻塞,直到已经提交的任务完成
    print('main',os.getpid())
  • 传参

    def func(i,name):
        print('start',os.getpid())
        time.sleep(random.randint(1,3))
        print('end', os.getpid())
        return '%s * %s'%(i,os.getpid())
    if __name__ == '__main__':
        p = ProcessPoolExecutor(5)
        ret_l = []
        for i in range(10):
            ret = p.submit(func,i,'alex')
            ret_l.append(ret)
        for ret in ret_l:
            print('ret-->',ret.result())  # ret.result() 同步阻塞
        print('main',os.getpid())
    

9.3线程池

  • 不传参

    import os
    import time
    import random
    from concurrent.futures import ThreadPoolExecutor
    def func(i):
        print('start', os.getpid())
        time.sleep(random.randint(1,3))
        print('end', os.getpid())
        return '%s * %s'%(i,os.getpid())
    tp = ThreadPoolExecutor(20)
    ret_l = []
    for i in range(10):
        ret = tp.submit(func,i)
        ret_l.append(ret)
    tp.shutdown()
    print('main')
    for ret in ret_l:
        print('------>',ret.result())
    
  • 传参

    from concurrent.futures import ThreadPoolExecutor
    def func(i):
        print('start', os.getpid())
        time.sleep(random.randint(1,3))
        print('end', os.getpid())
        return '%s * %s'%(i,os.getpid())
    tp = ThreadPoolExecutor(20)
    ret = tp.map(func,range(20))
    for i in ret:
        print(i)
    ret_l = []
    for i in range(10):
        ret = tp.submit(func,i)
        ret_l.append(ret)
    tp.shutdown()
    print('main')
    

9.4回调函数

  • ret.add_done_callback(函数名)

  • 要在ret对应的任务执行完毕之后,直接继续执行add_done_callback绑定的函数中的内容,并且ret的结果会作为参数返回给绑定的函数

import requests
from concurrent.futures import ThreadPoolExecutor
def get_page(url):
    res = requests.get(url)
    return {'url':url,'content':res.text}

def parserpage(ret):
    dic = ret.result()
    print(dic['url'])
tp = ThreadPoolExecutor(5)
url_lst = [
    'http://www.baidu.com',   # 3
    'http://www.cnblogs.com', # 1
    'http://www.douban.com',  # 1
    'http://www.tencent.com',
    'http://www.cnblogs.com/Eva-J/articles/8306047.html',
    'http://www.cnblogs.com/Eva-J/articles/7206498.html',
]
ret_l = []
for url in url_lst:
    ret = tp.submit(get_page,url)
    ret_l.append(ret)
    ret.add_done_callback(parserpage)
posted @ 2020-03-14 22:15  Hedger_Lee  阅读(89)  评论(0编辑  收藏  举报