python之路9:进程、线程、协程
- 进程
- 线程
- 协程
进程
什么是进程?
程序并不能单独运行,只有将程序装载到内存中,系统为它分配资源才能运行,而这种执行的程序就称之为进程。程序和进程的区别就在于:程序是指令的集合,它是进程运行的静态描述文本;进程是程序的一次执行活动,属于动态概念。
进程有两个主要缺陷:
- 进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。
- 进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
multiprocessing模块介绍
multiprocessing模块用来开启子进程,并在子进程中执行功能,该模块与多线程模块threading的编程接口类似。
multiprocessing的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
Process:
创建进程的类
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
PS:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组
kwargs表示调用对象的字典
name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。
如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,
需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
Process类的使用
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from multiprocessing import Process 6 import os 7 8 9 def info(title): 10 print(title) 11 print('module name:', __name__) 12 print('父进程id', os.getppid()) 13 print('当前进程id', os.getpid()) 14 print("\n") 15 16 17 def f(name): 18 info('function f') 19 print('hello', name) 20 21 22 # 一定要把开进程的代码写在if __name__=='__main__':下面 23 if __name__ == '__main__': 24 info('main process line') 25 p = Process(target=f, args=('lv',)) 26 p.start() 27 p.join()
多进程实现套接字并发
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from socket import * 6 from multiprocessing import Process 7 8 server = socket(AF_INET, SOCK_STREAM) 9 server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1) # 可以多次启动 10 server.bind(('localhost', 2000)) 11 server.listen(2) 12 13 14 def tell(conn, addr): 15 print("等待其它客户端的连接...") 16 while True: 17 try: 18 data = conn.recv(8192) 19 if not data: 20 print('有客户端已断开连接') 21 break 22 print("收到消息:", data.decode('utf-8')) 23 conn.send(data.upper()) 24 except BaseException as e: 25 print(e) 26 break 27 conn.close() 28 29 30 if __name__ == '__main__': 31 while True: 32 conn, addr = server.accept() 33 print("新的连接:", conn, addr) 34 p = Process(target=tell, args=(conn, addr)) 35 p.start() 36 server.close()
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from socket import * 6 7 client = socket(AF_INET, SOCK_STREAM) 8 client.connect(('localhost', 2000)) 9 10 while True: 11 msg = input(">>:").strip() 12 if len(msg) == 0: 13 continue 14 client.send(msg.encode('utf-8')) 15 data = client.recv(8192) 16 print("来自服务器:", data.decode('utf-8')) 17 client.close()
Queue:
队列都是在内存中操作,进程退出,队列清空。
队列有如下几种:
queue.Queue() #先进先出
queue.LifoQueue() #后进先出
queue.PriorityQueue() #优先级队列
queue.deque() #双线队列
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。参数maxsize是队列中允许最大项数。
方法介绍:
q.put方法用以插入数据到队列中,put方法还有两个可选参数:blocked和timeout。如果blocked为
True
(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。如果超时,会抛出Queue.Full异常。如果blocked为
False
,但该Queue已满,会立即抛出Queue.Full异常。
q.get方法可以从队列读取并且删除一个元素。同样,get方法有两个可选参数:blocked和timeout。如果blocked为
True
(默认值),并且timeout为正值,那么在等待时间内没有取到任何元素,会抛出Queue.Empty异常。如果blocked为
False
,有两种情况存在,如果Queue有一个值可用,则立即返回该值,否则,如果队列为空,则立即抛出Queue.Empty异常.
q.get_nowait():同q.get(
False
)
q.put_nowait():同q.put(
False
)
q.empty():调用此方法时q为空则返回
True
,该结果不可靠,比如在返回
True
的过程中,如果队列中又加入了项目。
q.full():调用此方法时q已满则返回
True
,该结果不可靠,比如在返回
True
的过程中,如果队列中的项目被取走。
q.qsize():返回队列中目前项目的正确数量,结果也不可靠,理由同q.empty()和q.full()一样
应用:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from multiprocessing import Process, Queue 6 import time 7 8 9 # 生产者做包子 10 def producter(q): 11 for i in range(10): 12 time.sleep(1) # 生产包子得有个过程 13 res = '包子%s' % i # 生产了这么多的包子 14 q.put(res) # 把生产出来的包子放进餐盘 15 print('生产者做包子', res) 16 q.put(None) # 只有生产者才知道什么时候就生产完了,放一个None进去说明此时已经生产完了 17 18 19 # 消费者吃包子 20 def consumer(q): 21 while True: # 假如消费者不断的吃 22 res = q.get() # 从餐盘取包子吃 23 if res is None:break # 如果吃的时候餐盘里面已经空了,就直接break了 24 time.sleep(2) 25 print('消费者吃包子', res) 26 27 28 if __name__ == '__main__': 29 q = Queue() 30 p1 = Process(target=producter, args=(q,)) 31 p2 = Process(target=consumer, args=(q,)) 32 p1.start() 33 p2.start() 34 p1.join() 35 p2.join() 36 print('main')
进程池
什么是进程池?进程池就是控制进程数目。对于远程过程调用的高级应用程序而言,应该使用进程池,Pool可以提供指定数量的进程,供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,就重用进程池中的进程。
创建进程池的类:如果指定numprocess为3,则进程池会从无到有创建三个进程,然后自始至终使用这三个进程去执行所有任务,不会开启其他进程。
Pool([numprocess [,initializer [, initargs]]]):创建进程池
参数介绍:
numprocess:要创建的进程数,如果省略,将默认为cpu_count()的值,可os.cpu_count()查看
initializer:是每个工作进程启动时要执行的可调用对象,默认为
None
initargs:是要传给initializer的参数组
p.
apply
(func [, args [, kwargs]]):在一个池工作进程中执行
func(
*
args,
*
*
kwargs),然后返回结果。
p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(
*
args,
*
*
kwargs),然后返回结果。此方法的结果是AsyncResult类的实例,
callback是可调用对象,接收输入参数。当func的结果变为可用时,
将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from multiprocessing import Pool 6 import os 7 8 9 def task(n): 10 print('[%s] is running' % os.getpid()) 11 print('[%s] is done' % os.getpid()) 12 return n 13 14 15 if __name__ == '__main__': 16 print(os.cpu_count()) # 查看cpu个数 17 p = Pool(4) # 最大四个进程 18 for i in range(1, 9): # 开8个任务 19 res = p.apply(task, args=(i,)) # 同步的,等着一个运行完才执行另一个 20 print('本次任务的结束:%s' % res) 21 p.close() # 禁止往进程池内在添加任务 22 p.join() # 在等进程池 23 print('主')
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from multiprocessing import Pool 6 import os 7 8 9 def walk(n): 10 print('task[%s] running...' % os.getpid()) 11 return n 12 13 14 if __name__ == '__main__': 15 p = Pool(8) 16 res_list = [] 17 for i in range(40): 18 res = p.apply_async(walk, args=(i,)) 19 # print(res) #打印出来的是对象 20 res_list.append(res) 21 p.close() # 禁止往进程池里添加任务 22 p.join() # 在等进程池 23 for obj in res_list: 24 print(obj.get())
回调函数:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from multiprocessing import Process, Pool 6 import time 7 import os 8 9 10 def foo(i): 11 time.sleep(1) 12 print('进程', os.getpid()) 13 return i + 1 14 15 16 def bar(arg): 17 print('-->exec done:', arg, os.getpid()) 18 19 20 if __name__ == '__main__': 21 pool = Pool(5) 22 print("主进程", os.getpid()) 23 for i in range(10): 24 pool.apply_async(func=foo, args=(i,), callback=bar) # callback回调函数,等foo执行完就调用bar函数,否则不调用 25 # pool.apply(func=foo, args=(i,)) 26 print('end') 27 pool.close() 28 pool.join() # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。
线程
什么是线程?
线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
线程与进程的区别如下:
线程共享创建它的进程的地址空间; 进程有自己的地址空间。
线程可以直接访问其进程的数据段; 进程拥有自己父进程数据段的副本。
线程可以直接与其进程的其他线程通信; 进程必须使用进程间通信来与兄弟进程通信。
新线程很容易创建; 新进程需要从父进程复制。
线程可以对同一进程的线程进行相当大的控制; 进程只能控制子进程。
对主线程的更改(取消,优先级更改等)可能会影响进程的其他线程的行为; 对父进程的更改不会影响子进程。
为什么要用多线程?
1. 创建线程比进程地址空间开销小
2. 线程启动的速度快
3.如果存在大量的计算和大量的I/O处理,拥有多个线程允许这些活动彼此重叠运行,从而会加快程序执行的速度
Python threading模块
线程有2种调用方式,如下:
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import threading 6 import time 7 8 9 def sayhi(num): # 定义每个线程要运行的函数 10 print("running on number:%s" % num) 11 time.sleep(1) 12 13 14 if __name__ == '__main__': 15 t1 = threading.Thread(target=sayhi, args=(4,)) # 生成一个线程实例 16 t2 = threading.Thread(target=sayhi, args=(8,)) # 生成另一个线程实例 17 t4 = threading.Thread(target=sayhi, args=(8,)) # 生成另一个线程实例 18 t1.start() # 启动线程 19 t4.start() # 启动另一个线程 20 t2.start() # 启动另一个线程 21 print(t1.getName()) # 获取线程名,线程id按照生成线程实例的顺序排名 22 print(t2.getName()) 23 print(t4.getName())
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import threading 6 import time 7 8 9 class MyThread(threading.Thread): 10 def __init__(self, num): 11 threading.Thread.__init__(self) 12 self.num = num 13 14 def run(self): # 定义每个线程要运行的函数 15 print("running on number:%s" % self.num) 16 time.sleep(1) 17 18 19 if __name__ == '__main__': 20 t1 = MyThread(4) 21 t2 = MyThread(8) 22 t1.start() 23 t2.start()
线程的方法:
threading.currentThread(): 返回当前的线程变量。
threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
threading.activeCount(): 返回正在运行的线程数量,与len(threading.enumerate())有相同的结果。
isAlive(): 返回线程是否活动的。
getName(): 返回线程名。
setName(): 设置线程名。
Join & Daemon
有些线程执行后台任务,例如发送keepalive数据包,或执行定期垃圾收集等等。 这些仅在主程序运行时才有用,并且一旦其他非守护程序线程退出就可以将它们终止。
如果没有守护程序线程,您必须跟踪它们,并在程序完全退出之前告诉它们退出。 通过将它们设置为守护程序线程,您可以让它们运行并忘记它们,当程序退出时,任何守护程序线程都会自动终止。
注意:守护程序线程在关闭时突然停止。 他们的资源(例如打开文件,数据库事务等)可能无法正确发布。 如果您希望线程正常停止,请将它们设置为非守护进程并使用合适的信号机制(如Event)。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import time 6 import threading 7 8 9 def run(n): 10 print('[%s]------running----\n' % n) 11 time.sleep(0.5) 12 print('--done--') 13 14 15 def main(): 16 for i in range(5): 17 t = threading.Thread(target=run, args=[i, ]) 18 t.start() 19 t.join(1) 20 print('starting thread', t.getName()) 21 22 23 m = threading.Thread(target=main, args=[]) 24 m.setDaemon(True) # 将main线程设置为Daemon线程,它做为程序主线程的守护线程,当主线程退出时,m线程也会退出,由m启动的其它子线程会同时退出,不管是否执行完任务 25 m.start() 26 m.join(timeout=2) 27 print("---main thread done----")
GIL VS Lock
加入GIL主要的原因是为了降低程序的开发的复杂度,比如现在的你写python不需要关心内存回收的问题,因为Python解释器帮你自动定期进行内存回收,你可以理解为python解释器里有一个独立的线程,每过一段时间它起wake up做一次全局轮询看看哪些内存数据是可以被清空的,此时你自己的程序 里的线程和 py解释器自己的线程是并发运行的,假设你的线程删除了一个变量,py解释器的垃圾回收线程在清空这个变量的过程中的clearing时刻,可能一个其它线程正好又重新给这个还没来及得清空的内存空间赋值了,结果就有可能新赋值的数据被删除了,为了解决类似的问题,python解释器简单粗暴的加了锁,即当一个线程运行时,其它人都不能动,这样就解决了上述的问题, 这可以说是Python早期版本的遗留问题。
python GIL(Global Interpreter Lock) 是全局的Cpython解释器锁。Lock是程序数据的锁,通常被用来实现对共享资源的同步访问。为每一个共享资源创建一个Lock对象,当你需要访问该资源时,调用acquire方法来获取锁对象(如果其它线程已经获得了该锁,则当前线程需等待其被释放),待资源访问完后,再调用release方法释放锁。import threading
mutex = threading.Lock()
mutex.aquire() #加锁
'''
对公共数据的操作
'''
mutex.release() #释放锁
如果不加锁:并发执行,速度快,但数据不安全。
加锁:加锁的部分串行执行,速度慢,但数据安全。
join:任务内的所有代码都是串行执行,速度慢,数据也安全。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import time 6 import threading 7 8 9 def addnum(): 10 global num # 在每个线程中都获取这个全局变量 11 print('--get num:', num) 12 time.sleep(1) 13 lock.acquire() # 修改数据前加锁 14 num -= 1 # 对此公共变量进行-1操作 15 lock.release() # 修改后释放 16 17 18 num = 10 # 设定一个共享变量 19 thread_list = [] 20 lock = threading.Lock() # 生成全局锁 21 for i in range(10): 22 t = threading.Thread(target=addnum) 23 t.start() 24 thread_list.append(t) 25 26 for t in thread_list: # 等待所有线程执行完毕 27 t.join() 28 29 print('final num:', num)
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import threading 6 import time 7 8 9 def run(n): 10 semaphore.acquire() 11 time.sleep(1) 12 print("run the thread: %s\n" % n) 13 semaphore.release() 14 15 16 if __name__ == '__main__': 17 num = 0 18 semaphore = threading.BoundedSemaphore(5) # 最多允许5个线程同时运行 19 for i in range(2): # 起3个线程 20 t = threading.Thread(target=run, args=(i,)) 21 t.start() 22 23 while threading.active_count() != 1: 24 print(threading.active_count()) 25 else: 26 print('----all threads done---') 27 print(num)
Event
对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为假。如果有线程等待一个Event对象, 而这个Event对象的标志为假,那么这个线程将会被一直阻塞直至该标志为真。一个线程如果将一个Event对象的信号标志设置为真,它将唤醒所有等待这个Event对象的线程。如果一个线程等待一个已经被设置为真的Event对象,那么它将忽略这个事件, 继续执行。方法介绍:
Event.isSet()
#返回event的状态值
Event.wait()
#如果event.isSet()==False将阻塞线程
Event.
set
()
#设置event的状态值为True,所有阻塞池的线程激活进入就绪状态,等待操作系统调度
Event.clear()
#Event.wait()将被阻塞,直到它再次被设置为止
queue
使用import queue,用法与进程Queue差不多。
queue.
Queue
(maxsize=0) #先进先出
queue.
LifoQueue
(maxsize=0)#后进先出
queue.
PriorityQueue
(maxsize=0) #存储数据时可设置优先级的队列,数字越小优先级越高。
多进程与多线程的区别:
多进程
优点:可以利用多核
缺点:开销大
多线程
优点:开销小
缺点:不可以利用多核
应用场景
计算密集型:也就是计算多,IO少,用多进程(如金融分析)
IO密集型:也就是IO多,计算少,就用多线程(如程序等待,网络io,磁盘读写)
concurrent.futuresconcurent.future模块是用来创建并行的任务,为了异步执行调用,既可以实现进程池,也可以实现线程池。
p = ProcessPoolExecutor(max_works)对于进程池如果不写max_works:默认的是cpu的数目,默认是4个
p = ThreadPoolExecutor(max_works)对于线程池如果不写max_works:默认的是cpu的数目*5
p.submit(task,i) #相当于apply_async异步方法
p.shutdown() #默认有个参数wite=True (相当于close和join)
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor 6 from threading import currentThread 7 import os 8 import time 9 import random 10 11 12 def task(n): 13 print('%s:%s is running' % (currentThread().getName(), os.getpid())) # 看到的pid都是一样的,因为线程是共享了一个进程 14 time.sleep(random.randint(1, 3)) # I/O密集型的,一般用线程,用了进程耗时长 15 return n 16 17 18 if __name__ == '__main__': 19 start = time.time() 20 # p = ProcessPoolExecutor() # 进程池,默认的是cpu的数目,默认是4个 21 p = ThreadPoolExecutor() # 线程池,如果不给定值,默认cup*5 22 l = [] 23 for i in range(10): # 10个任务(线程池效率比进程池高) 24 obj = p.submit(task, i) # 相当于apply_async异步方法 25 l.append(obj) 26 p.shutdown() # 默认有个参数wait=True (相当于close和join) 27 print('='*10) 28 print([obj.result() for obj in l]) 29 print(time.time() - start)
协程
什么是协程?
协程,又称微线程,纤程。协程是一种用户态的轻量级线程。线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
协程的好处:
-
无需线程上下文切换的开销
-
无需原子操作锁定及同步的开销
- 方便切换控制流
-
高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。
缺点:
-
无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
-
进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序
符合什么条件就能称之为协程:
-
必须在只有一个单线程里实现并发
-
修改共享数据不需加锁
-
用户程序里自己保存多个控制流的上下文栈
-
一个协程遇到IO操作自动切换到其它协程
Greenlet
greenlet是一个用C实现的协程模块,相比与python自带的yield,它可以使你在任意函数之间随意切换,而不需把这个函数先声明为generator
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from greenlet import greenlet 6 7 8 def test1(): 9 print(11) 10 gr2.switch() 11 print(33) 12 gr2.switch() 13 14 15 def test2(): 16 print(22) 17 gr1.switch() 18 print(44) 19 20 21 gr1 = greenlet(test1) 22 gr2 = greenlet(test2) 23 gr1.switch() # 手动切换
Gevent
Gevent 是一个第三方库,可以轻松通过gevent实现并发同步或异步编程,在gevent中用到的主要模式是Greenlet, 它是以C扩展模块形式接入Python的轻量级协程。 Greenlet全部运行在主程序操作系统进程的内部,但它们被协作式地调度。
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 import gevent 6 7 8 def task(pid): 9 gevent.sleep(0.5) 10 print('Task %s done' % pid) 11 12 13 def synchronous(): 14 for i in range(1, 3): 15 task(i) 16 17 18 def asynchronous(): 19 threads = [gevent.spawn(task, i) for i in range(3)] 20 gevent.joinall(threads) 21 22 23 print('Synchronous:') 24 synchronous() 25 26 print('Asynchronous:') 27 asynchronous() 28 29 ''' 30 上面程序的重要部分是将task函数封装到Greenlet内部线程的gevent.spawn。 初始化的greenlet列表存放在数组threads中, 31 此数组被传给gevent.joinall 函数,后者阻塞当前流程,并执行所有给定的greenlet。执行流程只会在所有greenlet执行完后才会 32 继续向下走。 33 '''
1 #!/usr/bin/env python 2 # -*- coding:utf-8 -*- 3 __author__ = 'BillyLV' 4 5 from gevent import monkey; monkey.patch_all() 6 import gevent 7 import time 8 9 10 def eat(name): 11 print('%s eat 1' % name) 12 time.sleep(2) # 我们用等待的时间模拟IO阻塞 13 # gevent.sleep(2) 14 print('%s eat 2' % name) 15 return '函数eat' 16 17 18 def play(name): 19 print('%s play 1' % name) 20 time.sleep(1) 21 # gevent.sleep(1) # 只能抓住gevent模块的阻塞 22 print('%s play 2' % name) 23 return '函数play' # 当有返回值的时候,gevent模块也提供了返回结果的操作 24 25 26 start = time.time() 27 g1 = gevent.spawn(eat, 'lv') # 创建一个协程对象g1,spawn括号内第一个参数是函数名,后面的参数是传给函数的 28 g2 = gevent.spawn(play, 'li') 29 # g1.join() # 等待g1结束 30 # g2.join() # 等待g2结束 31 32 # 上面等待的两句也可以这样写 33 gevent.joinall([g1, g2]) 34 35 print('运行时间', time.time()-start) 36 print(g1.value) # 拿到函数的返回值 37 print(g2.value)
参考:
http://www.cnblogs.com/alex3714
http://www.cnblogs.com/wupeiqi
internet&python books
PS:如侵权,联我删。