zoukankan      html  css  js  c++  java
  • 并发编程三

    线程池和进程池的shutdown

    from concurrent.futures import ThreadPoolExecutor
    import time
    
    pool = ThreadPoolExecutor(3)
    
    
    def task(name):
        print('%s 开始'%name)
        time.sleep(1)
        print('%s 结束'%name)
    
    
    if __name__ == '__main__':
        for i in range(20):
            pool.submit(task, '屌丝%s' % i)
    
        # 放到for外面
        pool.shutdown(wait=True)  # 等待所有任务完成,并且把池关闭
        # 问题,关了还能提交任务吗?不能再提交了
        # pool.submit(task,'sdddd')如果池子关闭后还继续提交,会报错
        print('主') # 立马执行,20个线程都执行完了,再执行
    

    定时器

    # 多长时间之后执行一个任务
    from threading import Timer
    def task(name):
        print('我是大帅比--%s'%name)
    if __name__ == '__main__':
        # t = Timer(2, task,args=('lqz',))  # 本质是开了一个线程,延迟一秒执行
        t.start()
    

    协程介绍

    进程,线程,协程
    # 协程是为了实现单线程下的并发,属于线程下,协程要解决的问题:保存状态+切换;   yield:生成器,只要函数中有yield关键字,这个函数就是生成器,通过yield可以实现保存状态+切换
    协程并不是真是存在的某个东西,而是程序员臆想出来的
    # 程序员控制,不让自己的程序遇到io,看上去,就实现并发了
    # 优点如下:
    协程的切换开销更小,属于程序级别的切换,操作系统完全感知不到,因而更加轻量级
    单线程内就可以实现并发的效果,最大限度地利用cpu
    # 缺点如下:
    协程的本质是单线程下,无法利用多核,可以是一个程序开启多个进程,每个进程内开启多个线程,每个线程内开启协程
    协程指的是单个线程,因而一旦协程出现阻塞,将会阻塞整个线程
    总结协程特点:
    必须在只有一个单线程里实现并发
    修改共享数据不需加锁
    用户程序里自己保存多个控制流的上下文栈(需要保存状态)
    附加:一个协程遇到IO操作自动切换到其它协程(如何实现检测IO,yield、greenlet都无法实现,就用到了gevent模块(select机制))
    

    greenlet模块

    from greenlet import greenlet
    import time
    # 遇到io不会切,初级模块,gevent模块基于它写的,处理io切换
    def eat():
        print('我吃了一口')
        time.sleep(1)
        # p.switch()
        print('我又吃了一口')
        # p.switch()
    
    
    def play():
        print('我玩了一会')
        e.switch()
        print('我又玩了一会')
    
    
    if __name__ == '__main__':
        e = greenlet(eat)
        p = greenlet(play)
        e.switch()
    # 一般不用这个模块
    

    gevent模块

    # 以后使用,这一句必须写
    from gevent import monkey;monkey.patch_all()
    import gevent
    import time
    
    def eat(name):
        print('%s 吃了一口' % name)
        time.sleep(1)  # io操作,被猴子补丁替换之后,gevent.sleep()
        print('%s 又吃了一口' % name)
    
    
    def play(name):
        print('%s 玩了一会' % name)
        time.sleep(2)
        print('%s 又玩了一会' % name)
    
    
    if __name__ == '__main__':
        ctim = time.time()
        e = gevent.spawn(eat,'lqz')
        p = gevent.spawn(play,'lqz')
        e.join() # 等待e执行完成
        p.join()
        print('主')
        print(time.time() - ctim)  #2.0165154933929443
    # 主要使用这个模块
    

    asyncio模块

    import time
    import asyncio
    
    # 把普通函数变成协程函数
    # 3.5以前这么写
    @asyncio.coroutine
    def task():
         print('开始了')
         yield from asyncio.sleep(1)  #asyncio.sleep(1)模拟io
         print('结束了')
     loop=asyncio.get_event_loop()  # 获取一个时间循环对象#
     # 协程函数加括号,并不会真正的去执行,它需要提交给loop,让loop循环着去执行
     # 协程函数列表
    ctime=time.time()
    t=[task(),task()]
    loop.run_until_complete(asyncio.wait(t))
    loop.close()
    print(time.time()-ctime)
    
    
    import time
    import asyncio
    from threading import current_thread
    # 表示我是协程函数,等同于3.5之前的装饰器
    async def task():
        print('开始了')
        print(current_thread().name)
        await asyncio.sleep(3)  # await等同于原来的yield from
        print('结束了')
    
    async def task2():
        print('开始了')
        print(current_thread().name)
        await asyncio.sleep(2)
        print('结束了')
    
    loop=asyncio.get_event_loop()
    ctime=time.time()
    t=[task(),task2()]
    loop.run_until_complete(asyncio.wait(t))
    loop.close()
    print(time.time()-ctime)
    

    i/o模型(面试重点)

    内存分为内核缓冲区和用户缓冲区(网络下载的资源,硬盘加载的资源,先放到内核缓冲区----》copy到应用程序的缓冲区,应用程序才能用这个数据),数据复制的过程中不会消耗CPU
    io模型:
    	-阻塞io(BIO)
        -非阻塞io(NIO)
        -io多路复用()  select(windows支持,windows不支持epoll,官方不提供redis的window版本),poll,epoll(linux支持,回调机制)
        -异步io:
        -信号驱动io(理论,不在考虑范围内)
    
    image-20200827153019660 image-20200827153237359
    # 阻塞io(BIO)   用户进程从发起请求,到最终拿到数据前,一直挂起等待; 数据会由用户进程完成拷贝
    举个例子:一个人去 商店买一把菜刀,
    他到商店问老板有没有菜刀(发起系统调用)
    如果有(表示在内核缓冲区有需要的数据)
    老板直接把菜刀给买家(从内核缓冲区拷贝到用户缓冲区)
    这个过程买家一直在等待
    
    如果没有,商店老板会向工厂下订单(IO操作,等待数据准备好)
    工厂把菜刀运给老板(进入到内核缓冲区)
    老板把菜刀给买家(从内核缓冲区拷贝到用户缓冲区)
    这个过程买家一直在等待
    是同步io
    
    image-20200827153759680
    # NIO – 非阻塞模式I/O  用户进程发起请求,如果数据没有准备好,那么立刻告知用户进程未准备好;此时用户进程可选择继续发起请求、或者先去做其他事情,稍后再回来继续发请求,直到被告知数据准备完毕,可以开始接收为止; 数据会由用户进程完成拷贝
    举个例子:一个人去 商店买一把菜刀,
    他到商店问老板有没有菜刀(发起系统调用)
    老板说没有,在向工厂进货(返回状态)
    买家去别地方玩了会,又回来问,菜刀到了么(发起系统调用)
    老板说还没有(返回状态)
    买家又去玩了会(不断轮询)
    最后一次再问,菜刀有了(数据准备好了)
    老板把菜刀递给买家(从内核缓冲区拷贝到用户缓冲区)
    
    整个过程轮询+等待:轮询时没有等待,可以做其他事,从内核缓冲区拷贝到用户缓冲区需要等待
    是同步io
    
    同一个线程,同一时刻只能监听一个socket,造成浪费,引入io多路复用,同时监听读个socket
    
    image-20200827154034546
    # IO Multiplexing - I/O多路复用模型   类似BIO,只不过找了一个代理,来挂起等待,并能同时监听多个请求; 数据会由用户进程完成拷贝
    举个例子:多个人去 一个商店买菜刀,
    多个人给老板打电话,说我要买菜刀(发起系统调用)
    老板把每个人都记录下来(放到select中)
    老板去工厂进货(IO操作)
    有货了,再挨个通知买到的人,来取刀(通知/返回可读条件)
    买家来到商店等待,老板把到给买家(从内核缓冲区拷贝到用户缓冲区)
    
    多路复用:老板可以同时接受很多请求(select模型最大1024个,epoll模型),
    但是老板把到给买家这个过程,还需要等待,
    是同步io
    
    在并发高的情况下,连接活跃度不高,epoll比select好,网站http的请求,连了就断掉
    并发性不高,同时连接很活跃,select比epoll好,websocket的连接,长连接,游戏开发
    
    image-20200827154344177
    # AIO – 异步I/O模型  发起请求立刻得到回复,不用挂起等待; 数据会由内核进程主动完成拷贝
    举个例子:还是买菜刀
    现在是网上下单到商店(系统调用)
    商店确认(返回)
    商店去进货(io操作)
    商店收到货把货发个卖家(从内核缓冲区拷贝到用户缓冲区)
    买家收到货(指定信号)
    
    整个过程无等待
    异步io
    AIO框架在windows下使用windows IOCP技术,在Linux下使用epoll多路复用IO技术模拟异步IO
    市面上多数的高并发框架,都没有使用异步io而是用的io多路复用,因为io多路复用技术很成熟且稳定,并且在实际的使用过程中,异步io并没有比io多路复用性能提升很多,没有达到很明显的程度,并且,真正的AIO编码难度比io多路复用高很多
    

  • 相关阅读:
    AngularJs 键盘事件和鼠标事件
    Linux的net.ipv4.tcp_timestamps参数
    实战:tcp链接rst场景tcpdump分析
    C++ map使用总结
    C++ 11常见功能介绍:auto,decltype,nullptr,for,lambda
    BLOCK层基本概念:bio,request,request_queue
    C: 字符数组中的空去掉
    代码静态分析工具-splint的学习与使用[转]
    代码分析工具splint安装介绍
    gcc/g++ 如何支持c11/c++11标准编译
  • 原文地址:https://www.cnblogs.com/feiguoguobokeyuan/p/13608690.html
Copyright © 2011-2022 走看看