进程与线程
为什么要有操作系统?
操作系统:操作系统是一个用来协调,管理和控制计算机硬件和软件资源的系统程序。位于底层硬件与应用软件之间
工作方式:向下管理硬件 向上提供接口
切换
1.出现IO时切换
2.固定时间切换
进程
定义: 进程就是一个程序在一个数据集上的一次动态执行过程。
组成: 进程一般由程序、数据集、进程控制块三部分组成。
程序: 我们编写的程序用来描述进程要完成哪些功能以及如何完成;
数据集: 则是程序在执行过程中所需要使用的资源;
进程控制块: 用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
线程
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元
组成:由线程ID、程序计数器、寄存器集合和堆栈共同组成。
线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
进程与线程的关系
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。
(2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。
(3)CPU分给线程,即真正在CPU上运行的是线程。
并行和并发
并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。 指系统具有处理多个任务(动作)的能力
并发处理(concurrency Processing) 指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行 是指系统具有同时处理多个任务(动作)的能力
同步与异步
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。举个例子,打电话时就是同步通信,发短息时就是异步通信。
线程 threading模块
Thread类直接创建
#直接通过多线程下的类实例出线程对象 #这个进程里面有三个线程,1个主线程,t1,t2两个子线程 #子线成和主线程是同步开启的,但主线程结束后,进程还没有关闭,子线程全部结束后进程才会关闭 import time import threading def countNum(n): # 定义某个线程要运行的函数 print("running on number:%s" %n) time.sleep(3) if __name__ == '__main__': t1 = threading.Thread(target=countNum,args=(23,)) #生成一个线程实例 t2 = threading.Thread(target=countNum,args=(34,)) t1.start() #启动线程 t2.start() print("ending!")
Thread类继承创建
import threading import time class MyThread(threading.Thread): def __init__(self,num): threading.Thread.__init__(self) self.num=num def run(self): #这个方法必须有,是start()方法里面触发的,没有run就会报错,和socket里面的handle()一样 print("running on number:%s" %self.num) time.sleep(3) print("running endind...") t1=MyThread(56) t2=MyThread(78) t1.start() t2.start() print("ending") #----------------- running on number:56 running on number:78 ending running endind... running endind...
Thread类方法
import threading import time def Music(name): print ("Begin {name}. {time}".format(name=name,time=time.ctime())) time.sleep(3) print("Ending {name}. {time}".format(name=name, time=time.ctime())) def Blog(name): print("Begin {name}. {time}".format(name=name, time=time.ctime())) time.sleep(5) print("Ending {name}. {time}".format(name=name, time=time.ctime())) t1 = threading.Thread(target=Music,args=('Alex',)) t2 = threading.Thread(target=Blog,args=('egon',)) t1.start() t2.start() t1.join() t2.join() print("end....") -----------------执行结果--------------- Begin Alex. Mon May 8 16:10:24 2017 Begin egon. Mon May 8 16:10:24 2017 Ending Alex. Mon May 8 16:10:27 2017 Ending egon. Mon May 8 16:10:29 2017 end.... -------------------------------------------------------------------------- import threading import time def Music(name): print ("Begin {name}. {time}".format(name=name,time=time.ctime())) time.sleep(3) print("Ending {name}. {time}".format(name=name, time=time.ctime())) def Blog(name): print("Begin {name}. {time}".format(name=name, time=time.ctime())) time.sleep(5) print("Ending {name}. {time}".format(name=name, time=time.ctime())) t1 = threading.Thread(target=Music,args=('Alex',)) t2 = threading.Thread(target=Blog,args=('egon',)) t1.start() t2.start() t1.join() print("end....") -----------------执行结果--------------- Begin Alex. Mon May 8 16:13:18 2017 Begin egon. Mon May 8 16:13:18 2017 Ending Alex. Mon May 8 16:13:21 2017 end.... Ending egon. Mon May 8 16:13:23 2017
# setDaemon(True): - 特点 将线程声明为守护线程,必须在start() 方法调用之前设置 1).如果子线程不设置为守护线程则如果子线程卡住,主程序也会被无限挂起。 2).但是有时候我们需要的是只要主线程完成了,不管子线程是否完成,都要和主线程一起退出,这时就可以用setDaemon方法 3).被设定守护的子线程理论上会和主线程一起运行结束,但是如果有多个子线程,而且不是全都守护线程的话, 主线程比子线程先结束的话主线程会等子线程执行完才会关闭进程,而且这个期间如果被守护的子线程能够执行完的话,他也会执行完 #事例一: (将两个子线程设置为守护线程,当主线程结束时,两个未执行完的子线程也结束) import threading import time def Music(name): print ("Begin {name}. {time}".format(name=name,time=time.ctime())) time.sleep(3) print("Ending {name}. {time}".format(name=name, time=time.ctime())) def Blog(name): print("Begin {name}. {time}".format(name=name, time=time.ctime())) time.sleep(5) print("Ending {name}. {time}".format(name=name, time=time.ctime())) threads = [] t1 = threading.Thread(target=Music,args=('Alex',)) t2 = threading.Thread(target=Blog,args=('egon',)) threads.append(t1) threads.append(t2) if __name__ == '__main__': for t in threads: t.setDaemon(True) #两个都设置为守护,不用管守护 t.start() print("end..") ---------------------执行结果------------------ Begin Alex. Thu Aug 17 18:08:37 2017 Begin egon. Thu Aug 17 18:08:37 2017 end.. #事例二:(t1设置为守护进程,t2执行完成后t1还未执行完,但这时t1也要随整个程序退出) import threading import time def Music(name): print("Begin {name}. {time}".format(name=name, time=time.ctime())) time.sleep(5) print("Ending {name}. {time}".format(name=name, time=time.ctime())) def Blog(name): print("Begin {name}. {time}".format(name=name, time=time.ctime())) time.sleep(3) print("Ending {name}. {time}".format(name=name, time=time.ctime())) threads = [] t1 = threading.Thread(target=Music, args=('Alex',)) t2 = threading.Thread(target=Blog, args=('egon',)) threads.append(t1) threads.append(t2) if __name__ == '__main__': t1.setDaemon(True) # t1设置为守护,不用管t1,当t2退出时,程序退出 for t in threads: t.start() print("end..") ---------------------执行结果------------------ Begin Alex. Thu Aug 17 18:39:59 2017 Begin egon. Thu Aug 17 18:39:59 2017 end.. Ending egon. Thu Aug 17 18:40:02 2017 #事例三: import threading import time def Music(name): print ("Begin {name}. {time}".format(name=name,time=time.ctime())) time.sleep(3) print("Ending {name}. {time}".format(name=name, time=time.ctime())) def Blog(name): print("Begin {name}. {time}".format(name=name, time=time.ctime())) time.sleep(5) print("Ending {name}. {time}".format(name=name, time=time.ctime())) threads = [] t1 = threading.Thread(target=Music,args=('Alex',)) t2 = threading.Thread(target=Blog,args=('egon',)) threads.append(t1) threads.append(t2) if __name__ == '__main__': t1.setDaemon(True) #t1设置为守护,不用管t1,当t2退出时,程序退出 for t in threads: t.start() print("end..") ---------------------执行结果------------------ Begin Alex. Mon May 8 16:57:45 2017 Begin egon. Mon May 8 16:57:45 2017 end.. Ending Alex. Mon May 8 16:57:48 2017 Ending egon. Mon May 8 16:57:50 2017
其它方法
1
2
3
4
5
6
7
8
9
|
Thread实例对象的方法 # isAlive(): 返回线程是否活动的。 # getName(): 返回线程名。 # setName(): 设置线程名。 threading模块提供的一些方法: # threading.currentThread(): 返回当前的线程变量。 # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。 # threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。 |
进程 multiprocessing
一.进程调用
import multiprocessing import time def f(name): time.sleep(1) print("hello",name,time.ctime()) # if __name__ == "main": if __name__ == '__main__': p_list = [] for i in range(3): p = multiprocessing.Process(target=f,args=("alex",)) p_list.append(p) p.start() for i in p_list: i.join() print("end")
from multiprocessing import Process import time class MyProcess(Process): def __init__(self ): super(MyProcess, self).__init__() # self.name = name def run(self): print ('hello', self.name,time.ctime()) time.sleep(1) if __name__ == '__main__': p_list=[] for i in range(3): p = MyProcess() p.start() p_list.append(p) for p in p_list: p.join()
二.process类
构造方法: 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:进程号。
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("------------------") time.sleep(1) def foo(name): info(name) 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")
三.进程间通信
进程彼此之间互相隔离,要实现进程间通信,即IPC,
multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
同步锁(Lock)
加入同步锁,使用多线程,只是在锁内是串行的,其它部分还是多线程的
import time import threading R = threading.Lock() def addNum(): global num R.acquire() temp=num time.sleep(0.1) num =temp-1 R.release() num = 100 thread_list = [] for i in range(100): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: t.join() print(num)
线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
import threading,time class myThread(threading.Thread): def doA(self): lockA.acquire() print(self.name,"gotlockA",time.ctime()) time.sleep(3) lockB.acquire() print(self.name,"gotlockB",time.ctime()) lockB.release() lockA.release() def doB(self): lockB.acquire() print(self.name,"gotlockB",time.ctime()) time.sleep(2) lockA.acquire() print(self.name,"gotlockA",time.ctime()) lockA.release() lockB.release() def run(self): self.doA() self.doB() if __name__=="__main__": lockA=threading.Lock() lockB=threading.Lock() threads=[] for i in range(5): threads.append(myThread()) for t in threads: t.start() for t in threads: t.join() print("end...")
import threading,time class myThread(threading.Thread): def doA(self): Rlock.acquire() print(self.name,"gotlockA",time.ctime()) time.sleep(1) Rlock.acquire() print(self.name,"gotlockB",time.ctime()) Rlock.release() Rlock.release() def doB(self): Rlock.acquire() print(self.name,"gotlockB",time.ctime()) time.sleep(1) Rlock.acquire() print(self.name,"gotlockA",time.ctime()) Rlock.release() def run(self): self.doA() time.sleep(1) self.doB() if __name__=="__main__": Rlock=threading.RLock() threads=[] for i in range(5): threads.append(myThread()) for t in threads: t.start() for t in threads: t.join() print("end...")
同步条件(Event)
import threading,time class Boss(threading.Thread): def run(self): print("BOSS:今晚大家都要加班到22:00。") print(event.isSet()) event.set() time.sleep(5) print("BOSS:<22:00>可以下班了。") print(event.isSet()) event.set() class Worker(threading.Thread): def run(self): event.wait() print("Worker:哎……命苦啊!") time.sleep(1) event.clear() event.wait() print("Worker:OhYeah!") if __name__=="__main__": event=threading.Event() threads=[] for i in range(5): threads.append(Worker()) threads.append(Boss()) for t in threads: t.start() for t in threads: t.join()
信号量(Semaphore)
import threading,time class myThread(threading.Thread): def run(self): if semaphore.acquire(): print(self.name) time.sleep(3) semaphore.release() if __name__=="__main__": semaphore=threading.Semaphore(5) thrs=[] for i in range(20): thrs.append(myThread()) for t in thrs: t.start()
多线程利器---队列(queue)
创建一个“队列”对象 import Queue q = Queue.Queue(maxsize = 10) Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。 将一个值放入队列中 q.put(10) 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。 将一个值从队列中取出 q.get() 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True, get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。 Python Queue模块有三种队列及构造函数: 1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize) 2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize) 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize) 此包中的常用方法(q = Queue.Queue()): q.qsize() 返回队列的大小 q.empty() 如果队列为空,返回True,反之False q.full() 如果队列满了,返回True,反之False q.full 与 maxsize 大小对应 q.get([block[, timeout]]) 获取队列,timeout等待时间 q.get_nowait() 相当q.get(False) 非阻塞 q.put(item) 写入队列,timeout等待时间 q.put_nowait(item) 相当q.put(item, False) q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号 q.join() 实际上意味着等到队列为空,再执行别的操作
import queue #先进后出 q=queue.LifoQueue() q.put(34) q.put(56) q.put(12) #优先级 # q=queue.PriorityQueue() # q.put([5,100]) # q.put([7,200]) # q.put([3,"hello"]) # q.put([4,{"name":"alex"}]) while 1: data=q.get() print(data)
生产者消费者模型:
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。
import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <10: print("making........") time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 #q.task_done() #q.join() print("ok......") def Consumer(name): count = 0 while count <10: time.sleep(random.randrange(4)) if not q.empty(): data = q.get() #q.task_done() #q.join() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) else: print("-----no baozi anymore----") count +=1 p1 = threading.Thread(target=Producer, args=('A',)) c1 = threading.Thread(target=Consumer, args=('B',)) c2 = threading.Thread(target=Consumer, args=('C',)) c3 = threading.Thread(target=Consumer, args=('D',)) p1.start() c1.start() c2.start() c3.start()
协程
协程 主要解决的是IO操作的,用户态切换
协程,又称微线程,纤程。英文名Coroutine。
优点1: 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
优点2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
一.yield的简单实现
import time def consumer(name): print("--->ready to eat baozi...") while True: new_baozi = yield print("%s is eating baozi %s" %(name,new_baozi)) time.sleep(1) def producer(): r = con.__next__() r = con2.__next__() n = 0 while 1: time.sleep(1) print("\033[32;1m[producer]\033[0m is maing baozi %s and %s "%(n,n+1)) con.send(n) con2.send(n+1) n += 2 if __name__ == '__main__': con = consumer("c1") con2 = consumer("c2") producer()
二.greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
from greenlet import greenlet def test1(): print(12) gr2.switch() print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1.switch() gr1 = greenlet(test1) gr2 = greenlet(test2) gr2.switch()
三.gevent
import gevent import requests,time start=time.time() def f(url): print('GET: %s' % url) resp =requests.get(url) data = resp.text print('%d bytes received from %s.' % (len(data), url)) gevent.joinall([ gevent.spawn(f, 'https://www.python.org/'), gevent.spawn(f, 'https://www.baidu.com/'), gevent.spawn(f, 'https://www.sina.com.cn/'), ]) print("cost time:",time.time()-start)
IO模型
同步(synchronous) IO
异步(asynchronous) IO
阻塞(blocking) IO
非阻塞(non-blocking)IO
对于一个network IO (这里我们以read举例),它会涉及到两个系统对象,一个是调用这个IO的process (or thread),另一个就是系统内核(kernel)。当一个read操作发生时,它会经历两个阶段:
1. 等待数据准备 (Waiting for the data to be ready)
2. 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
记住这两点很重要,因为这些IO Model的区别就是在两个阶段上各有不同的情况。
一. blocking IO (阻塞IO)
在linux中,默认情况下所有的socket都是blocking,一个典型的读操作流程大概是这样:
当用户进程调用了recvfrom这个系统调用,kernel就开始了IO的第一个阶段:准备数据。对于network io来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的UDP包),这个时候kernel就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当kernel一直等到数据准备好了,它就会将数据从kernel中拷贝到用户内存,然后kernel返回结果,用户进程才解除block的状态,重新运行起来。
所以,blocking IO的特点就是在IO执行的两个阶段都被block了。
二. non-blocking IO(非阻塞IO)
linux下,可以通过设置socket使其变为non-blocking。当对一个non-blocking socket执行读操作时,流程是这个样子:
从图中可以看出,当用户进程发出read操作时,如果kernel中的数据还没有准备好,那么它并不会block用户进程,而是立刻返回一个error。从用户进程角度讲 ,它发起一个read操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个error时,它就知道数据还没有准备好,于是它可以再次发送read操作。一旦kernel中的数据准备好了,并且又再次收到了用户进程的system call,那么它马上就将数据拷贝到了用户内存,然后返回。所以,用户进程其实是需要不断的主动询问kernel数据好了没有。
注意:
在网络IO时候,非阻塞IO也会进行recvform系统调用,检查数据是否准备好,与阻塞IO不一样,”非阻塞将大的整片时间的阻塞分成N多的小的阻塞, 所以进程不断地有机会 ‘被’ CPU光顾”。即每次recvform系统调用之间,cpu的权限还在进程手中,这段时间是可以做其他事情的,
也就是说非阻塞的recvform系统调用调用之后,进程并没有被阻塞,内核马上返回给进程,如果数据还没准备好,此时会返回一个error。进程在返回之后,可以干点别的事情,然后再发起recvform系统调用。重复上面的过程,循环往复的进行recvform系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
import time import socket sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) sk.setsockopt sk.bind(('127.0.0.1',6667)) sk.listen(5) sk.setblocking(False) while True: try: print ('waiting client connection .......') connection,address = sk.accept() # 进程主动轮询 print("+++",address) client_messge = connection.recv(1024) print(str(client_messge,'utf8')) connection.close() except Exception as e: print (e) time.sleep(4) #############################client import time import socket sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM) while True: sk.connect(('127.0.0.1',6667)) print("hello") sk.sendall(bytes("hello","utf8")) time.sleep(2) break
优点:能够在等待任务完成的时间里干其他活了(包括提交其他任务,也就是 “后台” 可以有多个任务在同时执行)。
缺点:任务完成的响应延迟增大了,因为每过一段时间才去轮询一次read操作,而任务可能在两次轮询之间的任意时间完成。这会导致整体数据吞吐量的降低。
三. IO multiplexing(IO多路复用)
IO multiplexing这个词可能有点陌生,但是如果我说select,epoll,大概就都能明白了。有些地方也称这种IO方式为event driven IO。我们都知道,select/epoll的好处就在于单个process就可以同时处理多个网络连接的IO。它的基本原理就是select/epoll这个function会不断的轮询所负责的所有socket,当某个socket有数据到达了,就通知用户进程。它的流程如图:
当用户进程调用了select,那么整个进程会被block,而同时,kernel会“监视”所有select负责的socket,当任何一个socket中的数据准备好了,select就会返回。这个时候用户进程再调用read操作,将数据从kernel拷贝到用户进程。
这个图和blocking IO的图其实并没有太大的不同,事实上,还更差一些。因为这里需要使用两个system call (select 和 recvfrom),而blocking IO只调用了一个system call (recvfrom)。但是,用select的优势在于它可以同时处理多个connection。(多说一句。所以,如果处理的连接数不是很高的话,使用select/epoll的web server不一定比使用multi-threading + blocking IO的web server性能更好,可能延迟还更大。select/epoll的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)
在IO multiplexing Model中,实际中,对于每一个socket,一般都设置成为non-blocking,但是,如上图所示,整个用户的process其实是一直被block的。只不过process是被select这个函数block,而不是被socket IO给block。
注意1:select函数返回结果中如果有文件可读了,那么进程就可以通过调用accept()或recv()来让kernel将位于内核中准备到的数据copy到用户区。
注意2: select的优势在于可以处理多个连接,不适用于单个连接
#***********************server.py import socket import select sk=socket.socket() sk.bind(("127.0.0.1",8801)) sk.listen(5) inputs=[sk,] while True: r,w,e=select.select(inputs,[],[],5) print(len(r)) for obj in r: if obj==sk: conn,add=obj.accept() print(conn) inputs.append(conn) else: data_byte=obj.recv(1024) print(str(data_byte,'utf8')) inp=input('回答%s号客户>>>'%inputs.index(obj)) obj.sendall(bytes(inp,'utf8')) print('>>',r) #***********************client.py import socket sk=socket.socket() sk.connect(('127.0.0.1',8801)) while True: inp=input(">>>>") sk.sendall(bytes(inp,"utf8")) data=sk.recv(1024) print(str(data,'utf8'))
import selectors import socket sel = selectors.DefaultSelector() #会根据操作系统自动选择一个IO多用复用模型 def accept(sock, mask): conn, addr = sock.accept() # Should be ready print('accepted', conn, 'from', addr) conn.setblocking(False) sel.register(conn, selectors.EVENT_READ, read) def read(conn, mask): data = conn.recv(1000) # Should be ready if data: print('echoing', repr(data), 'to', conn) conn.send(data) # Hope it won't block else: print('closing', conn) sel.unregister(conn) conn.close() sock = socket.socket() sock.bind(('127.0.0.1', 8080)) sock.listen(100) sock.setblocking(False) #设置非阻塞 #注册 把sock描述符和accept函数绑定 sel.register(sock, selectors.EVENT_READ, accept) while True: events = sel.select() for key, mask in events: callback = key.data callback(key.fileobj, mask) selectors
四 .Asynchronous I/O(异步IO)
异步最大特点:全程无阻塞
linux下的asynchronous IO其实用得很少。先看一下它的流程:
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。