python-进程、线程与协程
基础概念
进程
是一个执行中的程序,即将程序装载到内存中,系统为它分配资源的这一过程。进程是操作系统资源分配的基本单位。
每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。
-
- 文本区域存储处理器执行的代码;
- 数据区域存储变量和进程执行期间使用的动态分配的内存;
- 堆栈区域存储着活动过程调用的指令和本地变量。
进程状态
进程与程序
- 程序是指令数据的集合,是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念;
- 程序和进程无一一对应关系,一个程序可由多个进程共用,一个进程在活动中又可顺序地执行若干个程序;
- 进程是一个能独立运行的单位,能与其他进程并发执行,进程是作为资源申请和调度单位存在的;而通常的程序段不能作为一个独立运行的单位。
线程
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。
一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
python不管PC有几核,在同一核同一时刻执行的线程只有一个,python是调用系统的原生线程。
线程与进程
- 进程要操作CPU,必须要先创建一个线程,所有在同一个进程里的线程是共享同一块内存空间的;线程共享内存空间,进程的内存是独立的;
- 同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现;
- 创建新线程很简单, 创建新进程需要对其父进程进行一次克隆;
- 一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程。
同步与异步
同步:是指一个进程在执行某个请求的时候,若这个请求没有执行完成,那么这个进程将会一直等待下去,直到这个请求执行完毕,才会继续执行下面的请求。
异步:是指一个进程在执行某个请求的时候,如果这个请求没有执行完毕,进程不会等待,而是继续执行下面的请求。
并发与并行
并发:计算机的操作系统通过时间片轮转法等算法调度交替执行不同的任务。
并行:同时执行不同的任务。
多线程
线程调用方式
直接调用
import threading import time def run(*args): # 线程要运行的函数 print('test',args) time.sleep(3) t1 = threading.Thread(target=run, args=('t1',)) t2 = threading.Thread(target=run, args=('t2',)) t1.start() t2.start() print(t1.getName()) # Thread-1 print(t2.getName()) # Thread-2
继承式调用
1 class MyThread(threading.Thread): 2 3 def __init__(self, n ): 4 super(MyThread, self).__init__() 5 self.n = n 6 7 def run(self): # 必须写run函数 8 print(self.n) 9 time.sleep(2) 10 11 t1 = MyThread('t1') 12 t2 = MyThread('t2') 13 t1.start() 14 t2.start() 15 16 print(t1.getName()) 17 print(t2.getName())
线程方法
t.join(n) 表示主线程等待子线程多少时间,n表示主线程等待子线程的超时时间,如果在n时间内子线程未完成,主线程不在等待,执行后面的代码 t.run() 线程被cpu调度后自动执行线程对象的run方法(一般我们无需设置,除非自己定义类调用) t.start() 线程准备就绪,等待CPU调度 t.getName() 获取线程的名称 t.setName() 设置线程的名称 t.name 获取或设置线程的名称 t.is_alive() 判断线程是否为激活状态 t.isAlive() 判断线程是否为激活状态 t.isDaemon() 判断是否为守护线程 t.setDaemon() 是否设置守护线程,True表示主线程不等待子线程全部完成就执行后面的代码,False默认值,标识主线程等待子线程全部执行完后继续执行后面的代码 threading.current_thread() 当前线程详细信息 threading.active_count() 当前活跃线程数 threading.get_ident 获得线程号
threading.enumerate() 当前执行的线程列表
线程执行顺序
主线程启动子线程后,两者之间运行是并行的,默认主线程不会等待子线程运行结束,创建完成后继续往下执行,执行完后等待子线程全部执行完后退出程序。
import threading import time def run(*args): # 线程要运行的函数 print('test',args) time.sleep(3) print(args, 'is done') start_time = time.time() for i in range(3): t = threading.Thread(target=run, args=('t-%s' %i ,)) t.start() print("-------------------------------------------") print('run_time = ', time.time()- start_time) # test ('t-0',) # test ('t-1',) # test ('t-2',) # ------------------------------------------- # run_time = 0.0 # ('t-2',) is done # ('t-1',) is done # ('t-0',) is done
加入join,主线程会等待子线程执行完毕后继续往下执行,如果主线程需要子线程的返回结果,可以使用join。
import threading import time def run(*args): # 线程要运行的函数 print('running',args) time.sleep(3) print(args, 'is done') start_time = time.time() thread_pool = [] for i in range(2): t = threading.Thread(target=run, args=('t-%s' %i ,)) t.start() thread_pool.append(t) for i in thread_pool: i.join() print('------------------------------------------') print('run_time = ', time.time()- start_time) # running ('t-0',) # running ('t-1',) # ('t-1',) is done # ('t-0',) is done # ------------------------------------------ # run_time = 3.0156474113464355
join
join 参数:timeout 有n个设置join的子线程,就等待n倍timeout, 默认一直等待执行完毕
当没有设置守护线程时,主线程等待 N 倍timeout后继续往下执行,主线程执行完毕,子线程依然可以继续执行,执行完毕后退出程序;对于守护线程则是到时间就kill子线程。
1 import threading 2 import time 3 def run(*args): # 线程要运行的函数 4 print('running',args) 5 time.sleep(3) 6 print(args, 'is done') 7 8 start_time = time.time() 9 thread_pool = [] 10 for i in range(5): 11 t = threading.Thread(target=run, args=('t-%s' %i ,)) 12 t.setDaemon(True) 13 t.start() 14 thread_pool.append(t) 15 16 for i in thread_pool: 17 i.join(0.5) # 有n个线程就等待n 倍的timeout 18 19 print('------------------------------------------') 20 21 print('run_time = ', time.time()- start_time) 22 23 # 24 # running ('t-0',) 25 # running ('t-1',) 26 # running ('t-2',) 27 # running ('t-3',) 28 # running ('t-4',) 29 # ------------------------------------------ 30 # run_time = 2.531254768371582 31 # 32 # Process finished with exit code 0
1 import threading 2 import time 3 def run(*args): # 线程要运行的函数 4 print('running',args) 5 time.sleep(3) 6 print(args, 'is done') 7 8 start_time = time.time() 9 thread_pool = [] 10 for i in range(5): 11 t = threading.Thread(target=run, args=('t-%s' %i ,)) 12 t.setDaemon(True) 13 t.start() 14 thread_pool.append(t) 15 16 for i in thread_pool: 17 i.join(0.6) # 有n个线程就等待n 倍的timeout 18 19 print('------------------------------------------') 20 21 print('run_time = ', time.time()- start_time) 22 23 # #running ('t-0',) 24 # running ('t-1',) 25 # running ('t-2',) 26 # running ('t-3',) 27 # running ('t-4',) 28 # ('t-2',) is done 29 # ('t-1',) is done 30 # ('t-0',) is done 31 # ('t-4',) is done 32 # ('t-3',) is done 33 # ------------------------------------------ 34 # run_time = 3.021475076675415 35 # 36 # Process finished with exit code 0
守护线程
为主线程服务,主线程退出,不必等待守护线程的结束。
t.setDaemon(True) 把当前线程设置成守护线程 ,在t.start()之前设置
import threading import time def run(*args): # 线程要运行的函数 print('test',args) time.sleep(3) print(args, 'is done') start_time = time.time() for i in range(2): t = threading.Thread(target=run, args=('t-%s' %i ,)) t.setDaemon(True) t.start() print("-------------------------------------------") print('run_time = ', time.time()- start_time) # # test ('t-0',) # test ('t-1',) # ------------------------------------------- # run_time = 0.0 # Process finished with exit code 0
由守护线程创建的子线程为守护线程,可以通过t.isDaemon来判断。
import time import threading def run(n): print('[%s]------running----\n' % n) time.sleep(1) print('[%s]------done----\n' % n) def main(): for i in range(2): t = threading.Thread(target=run, args=[i, ]) t.start() print(i,t.isDaemon()) t.join() m = threading.Thread(target=main, args=[]) m.setDaemon(True) # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 m.start() m.join(timeout=2) print("---main thread done----") # [0]------running---- # 0 True # [0]------done---- # [1]------running---- # 1 True # ---main thread done----
GIL
无论启多少个线程,有多少个cpu,Python在执行的时候会淡定的在同一时刻只允许一个线程运行。
GIL(Global Interpreter Lock)是在实现Python解析器(CPython)时所引入的一个概念,同样一段代码可以通过CPython,PyPy,Psyco等不同的Python执行环境来执行。像其中的JPython就没有GIL。然而因为CPython是大部分环境下默认的Python执行环境。GIL并不是Python的特性,Python完全可以不依赖于GIL。
GIL对python多线程的影响: http://www.dabeaz.com/python/UnderstandingGIL.pdf
线程锁(互斥锁Mutex)
线程锁保证同一时刻只有一个线程修改内存空间的同一数据,GIL保证同一时刻只有一个线程在运行。
多线程同时修改同一数据,可能会导致数据最终结果不准确。
import time import threading def addNum(): global num # 在每个线程中都获取这个全局变量 time.sleep(1) num -= 1 # 对此公共变量进行-1操作 print('%s--get num:%s:'%(threading.current_thread().name,num )) num = 5 # 设定一个共享变量 thread_list = [] for i in range(5): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有线程执行完毕 t.join() print('final num:', num)
如果需要多个线程去修改同一数据,则需要给数据加一个线程锁。python3.x不加锁也不会出问题,但是建议加。如果修改数据量比较大的话,容易产生串行。
import time import threading lock = threading.Lock() def addNum(): lock.acquire() global num # 在每个线程中都获取这个全局变量 time.sleep(1) num -= 1 # 对此公共变量进行-1操作 print('%s--get num:%s'%(threading.current_thread().name,num )) lock.release() num = 5 # 设定一个共享变量 thread_list = [] for i in range(5): t = threading.Thread(target=addNum) t.start() thread_list.append(t) for t in thread_list: # 等待所有线程执行完毕 t.join() print('final num:', num)
递归锁
一个锁里面包含了子锁。
import time import threading lock = threading.RLock() def run2(): global num2 lock.acquire() num2 -= 1 lock.release() def addNum(): global num1 # 在每个线程中都获取这个全局变量 lock.acquire() run2() time.sleep(1) num1 -= 1 # 对此公共变量进行-1操作 print('%s--get num1:%s,num2:%s'%(threading.current_thread().name,num1,num2)) lock.release() num1, num2 = 5, 9 # 设定一个共享变量 thread_list = [] for i in range(5): t = threading.Thread(target=addNum) t.start() thread_list.append(t) while threading.active_count() != 1: print(threading.active_count()) time.sleep(1) else: print('----all threads done---') print('final num:', num1, num2)
信号量:Semaphore
互斥锁允许同一时刻只有一个线程修改数据,Semaphore 允许同一时刻运行一定数量的线程。
import time import threading semaphore = threading.BoundedSemaphore(3) def addNum(): semaphore.acquire() time.sleep(1) print('%s---running'%threading.current_thread().name) semaphore.release() for i in range(10): t = threading.Thread(target=addNum) t.start() while threading.active_count() != 1: pass else: print('----all threads done---')
Events
红绿灯,一个线程充当交通指挥灯,多个线程充当车辆,按照红灯停绿灯行的规则。注意event.set, event.clear放在打印灯和车的状态位置,否则容易出现红灯车也在跑的情况。
Event对象通过设置/清除标志位来实现和其他线程的同步,例如交通灯来修改Event的标志位来控制车辆线程的状态。
import threading, time, random events = threading.Event() def lighter(): if not events.isSet(): events.set() # 初始化绿灯Event set counter = 0 while True: if counter < 5: print('\033[42;0mGreen is lighten...\033[0m') elif counter < 10: if events.isSet(): events.clear() print('\033[41;0mRed is lighten...\033[0m') else: counter = 0 print('\033[42;1m--green light on---\033[0m') events.set() time.sleep(1) counter += 1 def car(i): while True: if events.isSet(): print("car[%s] is running..."%i) time.sleep(random.randrange(10)) else: print('car is waiting green lighten...') events.wait() if __name__ == '__main__': lighter1 = threading.Thread(target=lighter) lighter1.start() for i in range(3): t = threading.Thread(target=car, args=(i,)) t.start()
Event对象通过设置/清除标志位来实现和其他线程的同步,例如交通灯来修改Event的标志位来控制车辆线程的状态。
Event的几大方法:
event.set: 设置标志位为True event.clear: 清除标志位,标志位为False event.wait: 如果标志位为True,则不做操作;否则一直阻塞至标志位被设置为True event.isSet: 相当于event.is_set,如果标志位被设置,则返回True;否则返回False
queue
定义queue
class queue.Queue(maxsize=0) # 先入先出 class queue.LifoQueue(maxsize=0) # 后入先出 class queue.PriorityQueue(maxsize=0) # 按设置优先级获取数据 maxsize: 代表队列最多能存放的条目数,一旦达到maxsize队列将会阻塞,直到队列被被使用;0或者负数表示队列无穷大 优先级原则:最低值的条目是先检索的,最低值的条目是排序后返回的条目(列表(条目))[0]。条目的典型模式是表单中的元组:(priority_number, data)。
常用方法
Queue.qsize() # 返回队列大小 Queue.empty() # 如果队列为empty,则返回True Queue.full() # 如果队列为full,则返回True Queue.put(item, block=True, timeout=None) # 将item插入队列,默认block为True, timeout为None,如果队列为full,则会阻塞知道队列条目被get。 # 如果timeout是一个正数,将会等待timeout 秒,队列仍然为full,则抛出Full exception; # 如果block为false, 队列为full, 此时向队列插入数据时,直接抛出Full exception, 即使timeout设置为正数也将会被忽略。 Queue.put_nowait(item) # 等价于 put(item, False)
Queue.get(block=True, timeout=None) # 从队列中获取数据或者删除队列书中的数据,默认block为True,timeout为None,如果队列为空,则会阻塞直到队列为非空。 # 如果timeout是一个正数,将会等待timeout 秒,队列仍然为空,则抛出Empty exception; # 如果block为false,队列为空,此时从队列获取数据时,直接抛出Empty exception,即使timeout设置为正数也将会被忽略。 Queue.get_nowait() # 等价于 get(False)
exception queue.Empty exception queue.Full Queue.task_done() Queue.join() # block直到queue被消费完毕
参考: https://docs.python.org/3.5/library/queue.html#queue.Queue.task_done
多线程的应用
python多线程不适合cpu密集操作型任务,适合io操作密集型的任务。
io操作不占用cpu,计算占用cpu。
多进程
多进程的定义和多线程类似。每个进程都拥有一个独立的内存空间,所以多进程需要较大的开销。
from multiprocessing import Process import os def run(i): print('number: %s, process id: %s, parent id: %s, moudle name: %s'% (i, os.getpid(),os.getppid(),__name__)) p_num = [] if __name__ == '__main__': # 多进程win 系统需要加这句,否则报错 for i in range(10): p = Process(target=run, args=(i,)) p.start() p_num.append(p) for p in p_num: p.join() # 和多线程功能一样 print('main is done, moudle name: %s'%__name__)
number: 0, process id: 988, parent id: 25368, moudle name: __mp_main__ number: 1, process id: 5240, parent id: 25368, moudle name: __mp_main__ number: 3, process id: 21328, parent id: 25368, moudle name: __mp_main__ number: 2, process id: 24892, parent id: 25368, moudle name: __mp_main__ number: 4, process id: 25276, parent id: 25368, moudle name: __mp_main__ number: 5, process id: 24612, parent id: 25368, moudle name: __mp_main__ number: 7, process id: 12340, parent id: 25368, moudle name: __mp_main__ number: 6, process id: 24464, parent id: 25368, moudle name: __mp_main__ number: 8, process id: 20040, parent id: 25368, moudle name: __mp_main__ number: 9, process id: 20796, parent id: 25368, moudle name: __mp_main__ main is done, moudle name: __main__ Process finished with exit code 0
进程间通信
同一进程内的线程共享进程内存空间,可以直接访问同一数据;不同进程都拥有一个独立内存空间,要想实现两个进程间的数据交换,可通过以下方法:Queue,Pipe,Manager。
Queue
使用方法和线程queue一样,线程中的queue不能实现进程中的数据通信。
from multiprocessing import Process, Queue import os def run(q): print('process id: %s'% os.getpid()) q.put(os.getpid()) p_num = [] if __name__ == '__main__': # 多进程win 系统需要加这句,否则报错 q = Queue() for i in range(10): p = Process(target=run, args=(q,)) p.start() p_num.append(p) for p in p_num: p.join() # 和多线程功能一样 for i in range(10): print('main is done, q = %s'% q.get())
process id: 12976 process id: 27556 process id: 11424 process id: 28284 process id: 29056 process id: 14728 process id: 22856 process id: 26928 process id: 28496 process id: 6612 main is done, q = 27556 main is done, q = 12976 main is done, q = 11424 main is done, q = 28284 main is done, q = 29056 main is done, q = 14728 main is done, q = 22856 main is done, q = 26928 main is done, q = 28496 main is done, q = 6612 Process finished with exit code 0
Pipe
由Pipe()返回的两个连接对象表示管道的两端,每个连接对象都有send()和recv()方法。
注意,如果两个进程(或线程)试图同时读取或写入管道的同一端口,那么管道中的数据可能会被损坏;在同时使用不同端口的过程中也不会有风险。
from multiprocessing import Process, Pipe import os def run(conn): conn.send('process id: %s'%os.getpid()) conn.send('process done') conn.close() print('child process id: %s'% os.getpid()) if __name__ == '__main__': parent_conn, child_conn = Pipe() p = Process(target=run, args=(child_conn,)) p.start() print(parent_conn.recv()) print(parent_conn.recv()) # child process id: 10816 # process id: 10816 # process done
Manager
Manager 支持list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value and Array等数据类型。
from multiprocessing import Process, Manager import os def f(d, l): d['%s parent is %s'%(os.getpid(), os.getppid())] = os.getppid() l.append(os.getpid()) if __name__ == '__main__': with Manager() as manager: d = manager.dict() l = manager.list(range(3)) p_list = [] for i in range(5): p = Process(target=f, args=(d, l)) p.start() p_list.append(p) for res in p_list: res.join() print(list(d.keys())) # 以list类型返回字典的key print(l) # ['26500 parent is 29096', '23932 parent is 29096', '29540 parent is 29096', '27728 parent is 29096', '15348 parent is 29096'] # [0, 1, 2, 26500, 27728, 15348, 23932, 29540]
进程同步:进程锁
进程锁用来锁定输出,否则容易混淆,比如一个进程输出没完,另一个进程继续输出。
from multiprocessing import Process, Lock def f(l, i): l.acquire() print('hello world', i) l.release() if __name__ == '__main__': lock = Lock() for num in range(10): Process(target=f, args=(lock, num)).start()
进程池
进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进程,那么程序就会等待,直到进程池中有可用进程为止。
apply # 从进程池中获取的进程串行运行 apply_async # 从进程池中获取的进程并行运行
实例
from multiprocessing import Pool import time, os def f1(arg): time.sleep(1) print('process id %s:'%os.getpid(),arg) return arg # 传给Bar def Bar(args): # 由父进程执行 print('%s execute %s'%(os.getppid(),args)) if __name__ == '__main__': pool = Pool(5) for i in range(10): # pool.apply(func=f1,args=(i,)) # 所有进程串行执行 pool.apply_async(func=f1, args=(i,), callback=Bar) # 异步并行执行 pool.close() # 等待所有的任务执行完毕 # pool.terminate() # 立即终止子进程的任务,主进程继续执行 pool.join() # 执行pool.join时必须先执行pool.close或者pool.terminate。进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭close,terminate也无效 print('end')
process id 12704: 0 10608 execute 0 process id 3440: 1 10608 execute 1 process id 656: 2 10608 execute 2 process id 14652: 3 10608 execute 3 process id 15072: 4 10608 execute 4 process id 12704: 5 process id 3440: 6 10608 execute 5 10608 execute 6 process id 656: 7 10608 execute 7 process id 14652: 8 10608 execute 8 process id 15072: 9 10608 execute 9 end Process finished with exit code 0
协程
协程,又称微线程,纤程,是一种用户态的轻量级线程。
协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。
因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态(进入上一次离开时所处逻辑流的位置)
协程与线程类似,每个协程表示一个执行单元,既有自己的本地数据,也与其他协程共享全局数据和其他资源。
协程存在于线程中,需要用户来编写调度逻辑,对CPU而言,不需要考虑协程如何调度,切换上下文。
优点
- 无需线程上下文切换的开销
- 无需原子操作锁定及同步的开销
- 高并发+高扩展性+低成本
"原子操作(atomic operation)是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何上下文切换 (切换到另一个线程)。原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序是不可以被打乱,或者切割掉只执行部分。视作整体是原子性的核心。
不足
- 无法利用多核资源:协程的本质是个单线程,它不能同时将单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上。
- 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序。
yield
python通过yield提供了对协程的基本支持,但是并不完全。
import time import queue def consumer(name): print("--->starting eating baozi...") while True: new_baozi = yield print("[%s] is eating baozi %s" % (name, new_baozi)) # time.sleep(1) def producer(): input() r = con.__next__() r = con2.__next__() n = 0 while n < 5: n += 1 con.send(n) con2.send(n) print("\033[32;1m[producer]\033[0m is making baozi %s" % n) if __name__ == '__main__': con = consumer("c1") con2 = consumer("c2") p = producer()
greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator。
gevent对协程的支持,本质上是greenlet在实现切换工作。
greenlet的工作流程:进行访问网络的IO操作时,出现阻塞,greenlet就显式切换到另一段没有被阻塞的代码执行,直到原来的阻塞状况消失以后,再切换回原来代码段继续处理。因此,greenlet是一种合理安排的串行方法。
greenlet.switch()可实现协程的切换,greenlet并不能实现自动切换。
from greenlet import greenlet def test1(): print(12) gr2.switch() print(34) gr2.switch() def test2(): print(56) gr1.switch() print(78) gr1 = greenlet(test1) #启动一个携程 gr2 = greenlet(test2) gr1.switch() # C:\D\program\Python354\python.exe C:/D/personal_data/workspace/四/day10/greenlet携程.py # 12 # 56 # 34 # 78
gevent
gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程。gevent是对greenlet进行封装,实现协程的自动切换。
通过gevent.sleep模仿IO操作,实现协程的切换。
gevent.spawn 用来形成协程
gevent.joinall 添加这些协程任务,并且执行给定的gevent,同时阻塞当前程序流程,当所有gevent执行完毕程序继续向下执行
gevent.sleep 模拟IO操作多少时间
import gevent def foo(): print('Running in foo') gevent.sleep(2) print('Explicit context switch to foo again') def bar(): print('Explicit精确的 context内容 to bar') gevent.sleep(1) print('Implicit context switch back to bar') def func3(): print("running func3 ") gevent.sleep(0) print("running func3 again ") gevent.joinall([ gevent.spawn(foo), #生成, gevent.spawn(bar), gevent.spawn(func3), ]) # Running in foo # Explicit精确的 context内容 to bar # running func3 # running func3 again # Implicit context switch back to bar # Explicit context switch to foo again # # Process finished with exit code 0
上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。
初始化的greenlet列表存放在数组threads中,此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。
执行流程只会在 所有greenlet执行完后才会继续向下走。
monkey.patch_all()
协程切换是在IO操作时自动完成,在启动时通过monkey.patch_all()实现将一些常见的阻塞,如socket,select,urllib等地方实现协程跳转,因为gevent并不能完全识别所有当前操作是否为IO操作,而未切换。
import gevent import requests def run_task(url): print('Visit --> %s'% url) try: res = requests.get(url) data = res.text print('%s bytes received from %s' %(len(data),url)) except Exception as value: print(value) if __name__ == '__main__': urls = ['https://www.baidu.com/','https://github.com/','https://www.python.org/'] gevents = [ gevent.spawn(run_task, url) for url in urls] gevent.joinall(gevents) # # Visit --> https://www.baidu.com/ # 2443 bytes received from https://www.baidu.com/ # Visit --> https://github.com/ # 54833 bytes received from https://github.com/ # Visit --> https://www.python.org/ # 48703 bytes received from https://www.python.org/
monkey.patch_all() 相当于把当前程序的所有的io操作单独做上标记,完成自动切换。
from gevent import monkey; monkey.patch_all() import gevent import requests def run_task(url): print('Visit --> %s'% url) try: res = requests.get(url) data = res.text print('%s bytes received from %s' %(len(data),url)) except Exception as value: print(value) if __name__ == '__main__': urls = ['https://www.baidu.com/','https://github.com/','https://www.python.org/'] gevents = [ gevent.spawn(run_task, url) for url in urls] gevent.joinall(gevents) # Visit --> https://www.baidu.com/ # Visit --> https://github.com/ # Visit --> https://www.python.org/ # 2443 bytes received from https://www.baidu.com/ # 54833 bytes received from https://github.com/ # 48703 bytes received from https://www.python.org/
从上可以看出,没有monkey.path_all的情况无切换相当于串行,patch之后遇到IO操作自动切换,3个网络操作时并发执行,结束顺序不同,但其实只有一个线程。
gevent应用
gevent实现并发socket
# server import socket import gevent from gevent import monkey; monkey.patch_all() def handler(conn): try: while True: data = conn.recv(1024).decode() if len(data): print("receive: %s"%data) conn.send(data.upper().encode()) except Exception as value: print(value) HOST = ('0.0.0.0',9999) server = socket.socket() server.bind(HOST) server.listen() print('server start...') while True: conn,addr = server.accept() print('receive connection:%s'% conn) gevent.spawn(handler,conn) # client import socket client = socket.socket(socket.AF_INET,socket.SOCK_STREAM) client.connect(('localhost',9999)) while True: data = input('>>>').strip() client.send(data.encode()) data = client.recv(1024) print('receive: %s' %data)
gevent还提供对池的支持,当拥有动态数量的greenlet需要进行并发管理(限制并发数)时,就可以使用池,在处理大量的网络或IO操作时非常重要。
from gevent import monkey; monkey.patch_all() from gevent.pool import Pool import requests def run_task(url): print('Visit --> %s'% url) try: res = requests.get(url) data = res.text print('%s bytes received from %s' %(len(data),url)) except Exception as value: print(value) return 'url:%s --> finished' % url if __name__ == '__main__': urls = ['https://www.baidu.com/','https://github.com/','https://www.python.org/'] pool = Pool(2) result = pool.map(run_task, urls) print(result) Visit --> https://www.baidu.com/ Visit --> https://github.com/ 2443 bytes received from https://www.baidu.com/ Visit --> https://www.python.org/ 54833 bytes received from https://github.com/ 48703 bytes received from https://www.python.org/ ['url:https://www.baidu.com/ --> finished', 'url:https://github.com/ --> finished', 'url:https://www.python.org/ --> finished']