1 multiprocessing模块
(1.)直接导入 from multiprocessing import Process import os import time def info(name): print("name:",name) print('parent process:', os.getppid()) print('process id:', os.getpid()) print("------------------") def foo(name): info(name) time.sleep(50) if __name__ == '__main__': info('main process line') p1 = Process(target=info, args=('alvin',)) p2 = Process(target=foo, args=('egon',)) p1.start() p2.start() p1.join() p2.join() print("ending") time.sleep(100) >> name: main process line parent process: 16976 process id: 18456 ------------------ name: alvin parent process: 18456 process id: 19884 ------------------ name: egon parent process: 18456 process id: 19112 ------------------ ending
(2.)创建类的方法
构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
group: 线程组,目前还没有实现,库引用中提示必须是None;
target: 要执行的方法;
name: 进程名;
args/kwargs: 要传入方法的参数。
实例方法:
is_alive():返回进程是否在运行。
join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
start():进程准备就绪,等待CPU调度
run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。
terminate():不管任务是否完成,立即停止工作进程
属性:
daemon:和线程的setDeamon功能一样
name:进程名字。
pid:进程号。
2 协程
协程的优点:
(1) 由于单线程不存在切换
(2) 不再有任何锁的概念
yield是最基本的携程函数 没有办法监听到IO,进行切换 可以保存到数据的状态通过send方法来运行 import time # 注意到consumer函数是一个generator(生成器): # 任何包含yield关键字的函数都会自动成为生成器(generator)对象 def consumer(): r = '' while True: n = yield r if not n: return print('[CONSUMER] ←← Consuming %s...' % n) time.sleep(1) r = '200 OK' def produce(c): # 1、首先调用c.next()启动生成器 next(c) n = 0 while n < 5: n = n + 1 print('[PRODUCER] →→ Producing %s...' % n) # 2、然后,一旦生产了东西,通过c.send(n)切换到consumer执行; cr = c.send(n) # 4、produce拿到consumer处理的结果,继续生产下一条消息; print('[PRODUCER] Consumer return: %s' % cr) # 5、produce决定不生产了,通过c.close()关闭consumer,整个过程结束。 c.close() if __name__=='__main__': # 6、整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。 c = consumer() produce(c)
greenlet模块
可以实现手动切换
调用属性swich
gevent可以实现IO的监听
gevent.joinall 开启所有程序
gevent.spawn 切换
3 IO模型
IO指input, output
IO发生时涉及的对象和步骤
会涉及到两个系统对象,一个是调用这个IO的process(or thread),另一个就是系统内核(kernel)。当一个操作发生时,会经历两个阶段:
(1) 等待数据准备
(2) 将数据从内核拷贝到进程中
IO模型类型:
- 1. 阻塞 IO
- 1. 非阻塞 IO
非阻塞IO:发送多次系统调用
优点:wait for data无阻塞
缺点:系统调用太多
不能及时拿到数据
两个阶段:wait for data非阻塞
copy data 阻塞
非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
- 1. IO多路复用(监听多个链接)
特点:(1)全程阻塞
能监听多个文件描述符 实现并发
#服务端 import select import socket sock=socket.socket()#产生一个套接字 sock.bind(("127.0.0.1",8080)) sock.listen(5) sock.setblocking(False) inputs=[sock,] while 1: r,w,e=select.select(inputs,[],[])#监听有变化的套接字sock #wait for data for obj in r: if obj==sock: conn,addr=obj.accept()#从内核copy信息到用户态 print("conn",conn) inputs.append(conn)#监听列表添加客户conn else: data=obj.recv(1024)#接收信息 print(data.decode("utf8")) send_data=input(">>")#发送信息 obj.send(send_data.encode("utf8")) #客户端 import socket sock=socket.socket() sock.connect(("127.0.0.1",8080)) while 1: data=input("input>>") sock.send(data.encode("utf8")) recv_data=sock.recv(1024) print(recv_data.decode("utf8")) sock.close()
对于文件描述符(套接字对象)
(1) 是一个非零整数,不会变
(2) 收发数据的时候,对于接收端而言,数据先到内核空间,然后copy到用户空间,同时,内核空间数据清除
- 1. 异步IO
全程无阻塞
5.驱动信号
小结:
有阻塞blocking
无阻塞non-blocking
调用blocking IO会一直block住对应的进程知道操作完成
non-blocking IO在kernel还准备数据的情况下会立刻返回
有阻塞是同步阻塞:阻塞 非阻塞 IO多路复用
无阻塞是异步阻塞:异步IO
4 selectors模块
IO多路复用实现机制
Win:select
Linux:select,poll,epoll
Select缺点:1.每次调用select都要将所有的fd(文件描述符)拷贝到内核空间,导致效率下降
2.遍历所有的fd,是否有数据访问(最重要的问题)
3.最大连接数(1024)
poll:最大连接数没有限制
epoll:1.第一个函数创建epoll句柄,将所有的fd(文件描述符)拷贝到内核空间
只需要拷贝一次
2.回调函数:某一个函数或者某一个动作成功完成之后会触发的函数
为所有的fd绑定一个回调函数,但有数据访问触发该回调函数
回调函数将fd放到列表中
import selectors import socket sock=socket.socket() sock.bind(("127.0.0.1",8080)) sock.listen(5) sock.setblocking(False) sel=selectors.DefaultSelector()#根据具体平台选择最佳IO多路机制 def read(conn,mask): try: data=conn.recv(1024) print(data.decode("utf8")) data2=input(">>") conn.send(data2.encode("utf8")) except Exception: sel.unregister(conn) def accept(sock,mask): sel.register(sock,selectors.EVENT_READ,accept) conn,addr=sock.accept() sel.register(conn,selectors.EVENT_READ,read) sel.register(sock,selectors.EVENT_READ,accept)#注册功能 while 1: events=sel.select() for key,mask in events: print(key.data)#定义的函数 print(key.fileobj)#socket对象 func=key.data obj=key.fileobj func(obj,mask) break import socket sock=socket.socket() sock.connect(("127.0.0.1",8080)) while 1: data=input("input>>") sock.send(data.encode("utf8")) recv_data=sock.recv(1024) print(recv_data.decode("utf8")) sock.close()
5. 队列
队列用在多线程,多进程中,用来保护数据
队列是个数据类型
优点:线程安全
import queue q=queue.Queue(3)#默认是先进先出 q.put(111) q.put("hello") q.put(222) print(q.get()) print(q.get()) print(q.get()) >> 111 hello 222 import queue q=queue.Queue(3)#默认是先进先出 q.put(111) q.put("hello") q.put(222) q.put(223,False)#q=queue.Queue(3)队列定义只能放3个值, # #超过限额时,返回错误信息 print(q.get()) print(q.get()) print(q.get()) q.get()#没有数据的时候不会报错,只会等待 q.get(False)#数据为空,报错 先进后出 import queue q=queue.LifoQueue() q.put(111) q.put(5) q.put(43) print(q.get()) 优先级 import queue q=queue.PriorityQueue() q.put([4,"hello"]) q.put([1,"hello5"]) print(q.get())