并发编程三

线程池和进程池的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多路复用高很多

原文地址:https://www.cnblogs.com/feiguoguobokeyuan/p/13608690.html