python 进程 线程
进程 线程
操作系统
为什么要有操作系统?
操作系统:操作系统是一个用来协调,管理和控制计算机硬件和软件资源的系统程序。位于底层硬件与应用软件之间
工作方式:向下管理硬件 向上提供接口
切换
1.出现IO时切换
2.固定时间切换
进程
定义: 进程就是一个程序在一个数据集上的一次动态执行过程。
组成: 进程一般由程序、数据集、进程控制块三部分组成。
程序: 我们编写的程序用来描述进程要完成哪些功能以及如何完成;
数据集: 则是程序在执行过程中所需要使用的资源;
进程控制块: 用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
线程
线程的出现是为了降低上下文切换的消耗,提高系统的并发性,并突破一个进程只能干一样事的缺陷,使到进程内并发成为可能。
线程也叫轻量级进程,它是一个基本的CPU执行单元,也是程序执行过程中的最小单元
组成:由线程ID、程序计数器、寄存器集合和堆栈共同组成。
线程的引入减小了程序并发执行时的开销,提高了操作系统的并发性能。线程没有自己的系统资源。
进程与线程的关系
进程是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。或者说进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。
进程和线程的关系:
(1)一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。 (2)资源分配给进程,同一进程的所有线程共享该进程的所有资源。 (3)CPU分给线程,即真正在CPU上运行的是线程。
并行和并发
并行处理(Parallel Processing)是计算机系统中能同时执行两个或更多个处理的一种计算方法。并行处理可同时工作于同一程序的不同方面。并行处理的主要目的是节省大型和复杂问题的解决时间。 指系统具有处理多个任务(动作)的能力
并发处理(concurrency Processing) 指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机(CPU)上运行,但任一个时刻点上只有一个程序在处理机(CPU)上运行 是指系统具有同时处理多个任务(动作)的能力
同步与异步
同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;
异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率。举个例子,打电话时就是同步通信,发短息时就是异步通信。
线程对象的创建 threading模块
Thread类直接创建
View Code
1 import threading
2 import time
3
4 def countNum(n): # 定义某个线程要运行的函数
5 print("running on number:%s" %n)
6 time.sleep(3)
7
8 if __name__ == '__main__':
9
10 t1 = threading.Thread(target=countNum,args=(23,)) #创建一个线程对象
11 t2 = threading.Thread(target=countNum,args=(34,))
12
13 t1.start() #启动线程
14 t2.start()
15
16 print("ending!")
join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
1 import threading
2 import time
3
4 def Music(name):
5
6 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
7 time.sleep(3)
8 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
9
10 def Blog(name):
11 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
12 time.sleep(5)
13 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
14
15 t1 = threading.Thread(target=Music,args=('Alex',))
16 t2 = threading.Thread(target=Blog,args=('egon',))
17
18 t1.start()
19 t2.start()
20
21 t1.join()
22 t2.join()
23
24 print("end....")
25
26
27 -----------------执行结果---------------
28
29 Begin Alex. Mon May 8 16:10:24 2017
30 Begin egon. Mon May 8 16:10:24 2017
31 Ending Alex. Mon May 8 16:10:27 2017
32 Ending egon. Mon May 8 16:10:29 2017
33 end....
34
35 --------------------------------------------------------------------------
36
37 import threading
38 import time
39
40 def Music(name):
41
42 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
43 time.sleep(3)
44 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
45
46 def Blog(name):
47 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
48 time.sleep(5)
49 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
50
51 t1 = threading.Thread(target=Music,args=('Alex',))
52 t2 = threading.Thread(target=Blog,args=('egon',))
53
54 t1.start()
55 t2.start()
56
57 t1.join()
58
59
60 print("end....")
61
62 -----------------执行结果---------------
63
64 Begin Alex. Mon May 8 16:13:18 2017
65 Begin egon. Mon May 8 16:13:18 2017
66 Ending Alex. Mon May 8 16:13:21 2017
67 end....
68 Ending egon. Mon May 8 16:13:23 2017
69
70 join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
setDaemon(True)
1 import threading
2 import time
3
4 def Music(name):
5
6 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
7 time.sleep(3)
8 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
9
10 def Blog(name):
11 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
12 time.sleep(5)
13 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
14
15 t1 = threading.Thread(target=Music,args=('Alex',))
16 t2 = threading.Thread(target=Blog,args=('egon',))
17
18 t1.start()
19 t2.start()
20
21 t1.join()
22 t2.join()
23
24 print("end....")
25
26
27 -----------------执行结果---------------
28
29 Begin Alex. Mon May 8 16:10:24 2017
30 Begin egon. Mon May 8 16:10:24 2017
31 Ending Alex. Mon May 8 16:10:27 2017
32 Ending egon. Mon May 8 16:10:29 2017
33 end....
34
35 --------------------------------------------------------------------------
36
37 import threading
38 import time
39
40 def Music(name):
41
42 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
43 time.sleep(3)
44 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
45
46 def Blog(name):
47 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
48 time.sleep(5)
49 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
50
51 t1 = threading.Thread(target=Music,args=('Alex',))
52 t2 = threading.Thread(target=Blog,args=('egon',))
53
54 t1.start()
55 t2.start()
56
57 t1.join()
58
59
60 print("end....")
61
62 -----------------执行结果---------------
63
64 Begin Alex. Mon May 8 16:13:18 2017
65 Begin egon. Mon May 8 16:13:18 2017
66 Ending Alex. Mon May 8 16:13:21 2017
67 end....
68 Ending egon. Mon May 8 16:13:23 2017
69
70 join():在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
1 import threading
2 import time
3
4 def Music(name):
5
6 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
7 time.sleep(3)
8 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
9
10 def Blog(name):
11 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
12 time.sleep(5)
13 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
14
15 threads = []
16
17 t1 = threading.Thread(target=Music,args=('Alex',))
18 t2 = threading.Thread(target=Blog,args=('egon',))
19
20 threads.append(t1)
21 threads.append(t2)
22
23 if __name__ == '__main__':
24
25 for t in threads:
26 t.setDaemon(True) #两个都设置为守护,不用管守护
27 t.start()
28 print("end..")
29
30 ---------------------执行结果------------------
31 Begin Alex. Mon May 8 16:37:57 2017
32 Begin egon. Mon May 8 16:37:57 2017
33 end..
34
35
36
37 -----------------------------------------------------------------
38
39 import threading
40 import time
41
42 def Music(name):
43
44 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
45 time.sleep(3)
46 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
47
48 def Blog(name):
49 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
50 time.sleep(5)
51 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
52
53 threads = []
54
55 t1 = threading.Thread(target=Music,args=('Alex',))
56 t2 = threading.Thread(target=Blog,args=('egon',))
57
58 threads.append(t1)
59 threads.append(t2)
60
61 if __name__ == '__main__':
62
63 t1.setDaemon(True) #t1设置为守护,不用管t1,当t2退出时,程序退出
64 for t in threads:
65 t.start()
66 print("end..")
67
68 ---------------------执行结果------------------
69
70 Begin Alex. Mon May 8 16:57:45 2017
71 Begin egon. Mon May 8 16:57:45 2017
72 end..
73 Ending Alex. Mon May 8 16:57:48 2017
74 Ending egon. Mon May 8 16:57:50 2017
75
76
77
78
79 --------------------------------------------------
80 import threading
81 import time
82
83 def Music(name):
84
85 print ("Begin {name}. {time}".format(name=name,time=time.ctime()))
86 time.sleep(3)
87 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
88
89 def Blog(name):
90 print("Begin {name}. {time}".format(name=name, time=time.ctime()))
91 time.sleep(5)
92 print("Ending {name}. {time}".format(name=name, time=time.ctime()))
93
94 threads = []
95
96 t1 = threading.Thread(target=Music,args=('Alex',))
97 t2 = threading.Thread(target=Blog,args=('egon',))
98
99 threads.append(t1)
100 threads.append(t2)
101
102 if __name__ == '__main__':
103
104 t2.setDaemon(True)
105 for t in threads:
106 t.start()
107 print("end..")
108
109 ---------------------执行结果------------------
110 Begin Alex. Mon May 8 16:58:05 2017
111 Begin egon. Mon May 8 16:58:05 2017
112 end..
113 Ending Alex. Mon May 8 16:58:08 2017
114
115
116
117
118
119 setDaemon(True)
进程 multiprocessing
一.进程调用
Process类调用
1 import multiprocessing
2 import time
3
4 def f(name):
5 time.sleep(1)
6 print("hello",name,time.ctime())
7
8 # if __name__ == "main":
9
10 if __name__ == '__main__':
11 p_list = []
12 for i in range(3):
13 p = multiprocessing.Process(target=f,args=("alex",))
14 p_list.append(p)
15 p.start()
16
17 for i in p_list:
18 i.join()
19 print("end")
20
21 Process类调用
继承Process类调用
1 from multiprocessing import Process
2 import time
3
4 class MyProcess(Process):
5 def __init__(self ):
6 super(MyProcess, self).__init__()
7 # self.name = name
8
9 def run(self):
10
11 print ('hello', self.name,time.ctime())
12 time.sleep(1)
13
14 if __name__ == '__main__':
15 p_list=[]
16 for i in range(3):
17 p = MyProcess()
18 p.start()
19 p_list.append(p)
20
21 for p in p_list:
22 p.join()
二.process类
Process类说明
1 构造方法:
2
3 Process([group [, target [, name [, args [, kwargs]]]]])
4
5 group: 线程组,目前还没有实现,库引用中提示必须是None;
6 target: 要执行的方法;
7 name: 进程名;
8 args/kwargs: 要传入方法的参数。
9
10 实例方法:
11
12 is_alive():返回进程是否在运行。
13
14 join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
15
16 start():进程准备就绪,等待CPU调度
17
18 run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。
19
20 terminate():不管任务是否完成,立即停止工作进程
21
22 属性:
23
24 daemon:和线程的setDeamon功能一样
25
26 name:进程名字。
27
28 pid:进程号。
代码示例
1 from multiprocessing import Process
2 import os
3 import time
4 def info(name):
5
6
7 print("name:",name)
8 print('parent process:', os.getppid())
9 print('process id:', os.getpid())
10 print("------------------")
11 time.sleep(1)
12
13 def foo(name):
14
15 info(name)
16
17 if __name__ == '__main__':
18
19 info('main process line')
20
21
22 p1 = Process(target=info, args=('alvin',))
23 p2 = Process(target=foo, args=('egon',))
24 p1.start()
25 p2.start()
26
27 p1.join()
28 p2.join()
29
30 print("ending")
三.进程间通信
进程彼此之间互相隔离,要实现进程间通信,即IPC,
multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的
同步锁(Lock)
加入同步锁,使用多线程,只是在锁内是串行的,其它部分还是多线程的
同步锁例子
1 import time
2 import threading
3
4
5 R = threading.Lock()
6 def addNum():
7 global num
8 R.acquire()
9 temp=num
10 time.sleep(0.1)
11 num =temp-1
12 R.release()
13
14 num = 100
15 thread_list = []
16
17 for i in range(100):
18 t = threading.Thread(target=addNum)
19 t.start()
20 thread_list.append(t)
21
22 for t in thread_list:
23 t.join()
24
25 print(num)
线程死锁和递归锁
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁,因为系统判断这部分资源都正在使用,所有这两个线程在无外力作用下将一直等待下去。下面是一个死锁的例子:
死锁例子
1 import threading,time
2
3 class myThread(threading.Thread):
4 def doA(self):
5
6 Rlock.acquire()
7 print(self.name,"gotlockA",time.ctime())
8 time.sleep(1)
9 Rlock.acquire()
10 print(self.name,"gotlockB",time.ctime())
11 Rlock.release()
12 Rlock.release()
13
14 def doB(self):
15
16 Rlock.acquire()
17 print(self.name,"gotlockB",time.ctime())
18 time.sleep(1)
19 Rlock.acquire()
20 print(self.name,"gotlockA",time.ctime())
21 Rlock.release()
22
23 def run(self):
24 self.doA()
25 time.sleep(1)
26 self.doB()
27 if __name__=="__main__":
28
29 Rlock=threading.RLock()
30 threads=[]
31 for i in range(5):
32 threads.append(myThread())
33 for t in threads:
34 t.start()
35 for t in threads:
36 t.join()
37
38 print("end...")
递归例子 。解决死锁
1 import threading,time
2
3 class myThread(threading.Thread):
4 def doA(self):
5
6 Rlock.acquire()
7 print(self.name,"gotlockA",time.ctime())
8 time.sleep(1)
9 Rlock.acquire()
10 print(self.name,"gotlockB",time.ctime())
11 Rlock.release()
12 Rlock.release()
13
14 def doB(self):
15
16 Rlock.acquire()
17 print(self.name,"gotlockB",time.ctime())
18 time.sleep(1)
19 Rlock.acquire()
20 print(self.name,"gotlockA",time.ctime())
21 Rlock.release()
22
23 def run(self):
24 self.doA()
25 time.sleep(1)
26 self.doB()
27 if __name__=="__main__":
28
29 Rlock=threading.RLock()
30 threads=[]
31 for i in range(5):
32 threads.append(myThread())
33 for t in threads:
34 t.start()
35 for t in threads:
36 t.join()
37
38 print("end...")
同步条件(Event)
Event 例子
1 import threading,time
2 class Boss(threading.Thread):
3 def run(self):
4 print("BOSS:今晚大家都要加班到22:00。")
5 print(event.isSet())
6 event.set()
7 time.sleep(5)
8 print("BOSS:<22:00>可以下班了。")
9 print(event.isSet())
10 event.set()
11 class Worker(threading.Thread):
12 def run(self):
13 event.wait()
14 print("Worker:哎……命苦啊!")
15 time.sleep(1)
16 event.clear()
17 event.wait()
18 print("Worker:OhYeah!")
19 if __name__=="__main__":
20 event=threading.Event()
21 threads=[]
22 for i in range(5):
23 threads.append(Worker())
24 threads.append(Boss())
25 for t in threads:
26 t.start()
27 for t in threads:
28 t.join()
信号量(Semaphore)
Semaphore 例子
1 import threading,time
2 class myThread(threading.Thread):
3 def run(self):
4 if semaphore.acquire():
5 print(self.name)
6 time.sleep(3)
7 semaphore.release()
8 if __name__=="__main__":
9 semaphore=threading.Semaphore(5)
10 thrs=[]
11 for i in range(20):
12 thrs.append(myThread())
13 for t in thrs:
14 t.start()
多线程利器---队列(queue)
queue.Queue
1 创建一个“队列”对象
2 import Queue
3 q = Queue.Queue(maxsize = 10)
4 Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。
5
6 将一个值放入队列中
7 q.put(10)
8 调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
9 1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。
10
11 将一个值从队列中取出
12 q.get()
13 调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,
14 get()就使调用线程暂停,直至有项目可用。如果队列为空且block为False,队列将引发Empty异常。
15
16 Python Queue模块有三种队列及构造函数:
17 1、Python Queue模块的FIFO队列先进先出。 class queue.Queue(maxsize)
18 2、LIFO类似于堆,即先进后出。 class queue.LifoQueue(maxsize)
19 3、还有一种是优先级队列级别越低越先出来。 class queue.PriorityQueue(maxsize)
20
21 此包中的常用方法(q = Queue.Queue()):
22 q.qsize() 返回队列的大小
23 q.empty() 如果队列为空,返回True,反之False
24 q.full() 如果队列满了,返回True,反之False
25 q.full 与 maxsize 大小对应
26 q.get([block[, timeout]]) 获取队列,timeout等待时间
27 q.get_nowait() 相当q.get(False)
28 非阻塞 q.put(item) 写入队列,timeout等待时间
29 q.put_nowait(item) 相当q.put(item, False)
30 q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
31 q.join() 实际上意味着等到队列为空,再执行别的操作
View Code
1 import queue
2
3 #先进后出
4
5 q=queue.LifoQueue()
6
7 q.put(34)
8 q.put(56)
9 q.put(12)
10
11 #优先级
12 # q=queue.PriorityQueue()
13 # q.put([5,100])
14 # q.put([7,200])
15 # q.put([3,"hello"])
16 # q.put([4,{"name":"alex"}])
17
18 while 1:
19
20 data=q.get()
21 print(data)
生产者消费者模型:
为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户去饭菜也不需要不找厨师,直接去前台领取即可,这也是一个结耦的过程。
生产包子 消费包子
1 import time,random
2 import queue,threading
3
4 q = queue.Queue()
5
6 def Producer(name):
7 count = 0
8 while count <10:
9 print("making........")
10 time.sleep(random.randrange(3))
11 q.put(count)
12 print('Producer %s has produced %s baozi..' %(name, count))
13 count +=1
14 #q.task_done()
15 #q.join()
16 print("ok......")
17
18 def Consumer(name):
19 count = 0
20 while count <10:
21 time.sleep(random.randrange(4))
22 if not q.empty():
23 data = q.get()
24 #q.task_done()
25 #q.join()
26 print(data)
27 print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data))
28 else:
29 print("-----no baozi anymore----")
30 count +=1
31
32 p1 = threading.Thread(target=Producer, args=('A',))
33 c1 = threading.Thread(target=Consumer, args=('B',))
34 c2 = threading.Thread(target=Consumer, args=('C',))
35 c3 = threading.Thread(target=Consumer, args=('D',))
36 p1.start()
37 c1.start()
38 c2.start()
39 c3.start()
协程
协程 主要解决的是IO操作的,用户态切换
协程,又称微线程,纤程。英文名Coroutine。
优点1: 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
优点2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
因为协程是一个线程执行,那怎么利用多核CPU呢?最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
一.yield的简单实现
yield 实现协程
1 import time
2
3 def consumer(name):
4 print("--->ready to eat baozi...")
5 while True:
6 new_baozi = yield
7 print("%s is eating baozi %s" %(name,new_baozi))
8 time.sleep(1)
9
10 def producer():
11 r = con.__next__()
12 r = con2.__next__()
13
14 n = 0
15 while 1:
16 time.sleep(1)
17 print("\033[32;1m[producer]\033[0m is maing baozi %s and %s "%(n,n+1))
18 con.send(n)
19 con2.send(n+1)
20
21 n += 2
22
23 if __name__ == '__main__':
24 con = consumer("c1")
25 con2 = consumer("c2")
26 producer()
二.greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
Greenlet 需要手动切换
1 from greenlet import greenlet
2
3 def test1():
4 print(12)
5 gr2.switch()
6 print(34)
7 gr2.switch()
8
9 def test2():
10 print(56)
11 gr1.switch()
12 print(78)
13 gr1.switch()
14
15 gr1 = greenlet(test1)
16 gr2 = greenlet(test2)
17
18 gr2.switch()
三.gevent
gevent 协程 爬虫
1 import gevent
2
3 import requests,time
4
5 start=time.time()
6
7 def f(url):
8 print('GET: %s' % url)
9 resp =requests.get(url)
10 data = resp.text
11 print('%d bytes received from %s.' % (len(data), url))
12
13 gevent.joinall([
14
15 gevent.spawn(f, 'https://www.python.org/'),
16 gevent.spawn(f, 'https://www.baidu.com/'),
17 gevent.spawn(f, 'https://www.sina.com.cn/'),
18
19 ])
20
21
22 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系统调用。这个过程通常被称之为轮询。轮询检查内核数据,直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个过程,进程仍然是属于阻塞的状态。
View Code
1 import time
2 import socket
3 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
4 sk.setsockopt
5 sk.bind(('127.0.0.1',6667))
6 sk.listen(5)
7 sk.setblocking(False)
8 while True:
9 try:
10 print ('waiting client connection .......')
11 connection,address = sk.accept() # 进程主动轮询
12 print("+++",address)
13 client_messge = connection.recv(1024)
14 print(str(client_messge,'utf8'))
15 connection.close()
16 except Exception as e:
17 print (e)
18 time.sleep(4)
19
20 #############################client
21
22 import time
23 import socket
24 sk = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
25
26 while True:
27 sk.connect(('127.0.0.1',6667))
28 print("hello")
29 sk.sendall(bytes("hello","utf8"))
30 time.sleep(2)
31 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的优势在于可以处理多个连接,不适用于单个连接
View Code
1 #***********************server.py
2 import socket
3 import select
4 sk=socket.socket()
5 sk.bind(("127.0.0.1",8801))
6 sk.listen(5)
7 inputs=[sk,]
8 while True:
9 r,w,e=select.select(inputs,[],[],5)
10 print(len(r))
11
12 for obj in r:
13 if obj==sk:
14 conn,add=obj.accept()
15 print(conn)
16 inputs.append(conn)
17 else:
18 data_byte=obj.recv(1024)
19 print(str(data_byte,'utf8'))
20 inp=input('回答%s号客户>>>'%inputs.index(obj))
21 obj.sendall(bytes(inp,'utf8'))
22
23 print('>>',r)
24
25 #***********************client.py
26
27 import socket
28 sk=socket.socket()
29 sk.connect(('127.0.0.1',8801))
30
31 while True:
32 inp=input(">>>>")
33 sk.sendall(bytes(inp,"utf8"))
34 data=sk.recv(1024)
35 print(str(data,'utf8'))
selectors
1 import selectors
2 import socket
3
4 sel = selectors.DefaultSelector() #会根据操作系统自动选择一个IO多用复用模型
5
6 def accept(sock, mask):
7 conn, addr = sock.accept() # Should be ready
8 print('accepted', conn, 'from', addr)
9 conn.setblocking(False)
10 sel.register(conn, selectors.EVENT_READ, read)
11
12 def read(conn, mask):
13 data = conn.recv(1000) # Should be ready
14 if data:
15 print('echoing', repr(data), 'to', conn)
16 conn.send(data) # Hope it won't block
17 else:
18 print('closing', conn)
19 sel.unregister(conn)
20 conn.close()
21
22 sock = socket.socket()
23 sock.bind(('127.0.0.1', 8080))
24 sock.listen(100)
25 sock.setblocking(False) #设置非阻塞
26
27 #注册 把sock描述符和accept函数绑定
28 sel.register(sock, selectors.EVENT_READ, accept)
29
30 while True:
31 events = sel.select()
32 for key, mask in events:
33 callback = key.data
34 callback(key.fileobj, mask)
四 .Asynchronous I/O(异步IO)
异步最大特点:全程无阻塞
linux下的asynchronous IO其实用得很少。先看一下它的流程:
用户进程发起read操作之后,立刻就可以开始去做其它的事。而另一方面,从kernel的角度,当它受到一个asynchronous read之后,首先它会立刻返回,所以不会对用户进程产生任何block。然后,kernel会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel会给用户进程发送一个signal,告诉它read操作完成了。