Python多进程multiprocessing使用示例

参考:http://outofmemory.cn/code-snippet/2267/Python-duojincheng-multiprocessing-usage-example

参考:http://blog.csdn.net/qdx411324962/article/details/46810421

参考:http://www.lxway.com/4488626156.htm

廖雪峰官网 进程和线程、多进程、多线程、ThreadLocal、进程 vs. 线程、分布式进程

multiprocessing 文档: https://docs.python.org/2/library/multiprocessing.html#managers

Python 中的进程、线程、协程、同步、异步、回调:https://segmentfault.com/a/1190000001813992


由于要做把一个多线程改成多进程,看一下相关方面的东西,总结一下,主要是以下几个相关的标准库

  1. subprocess
  2. signal
  3. threading
  4. multiprocessing

python 单线程 和 多线程

python单线程

[python]  view plain  copy
  1. # -*- coding:utf-8 -*-  
  2. from time import ctime, sleep  
  3.   
  4.   
  5. def music(argv):  
  6.     for i in range(2):  
  7.         print "listen music  %s. %s" % (argv, ctime())  
  8.         sleep(1)  
  9.   
  10.   
  11. def movie(argv):  
  12.     for i in range(2):  
  13.         print "watch movie!  %s. %s" % (argv, ctime())  
  14.         sleep(5)  
  15.   
  16. if __name__ == '__main__':  
  17.     music(u'trouble is a friend')  
  18.     movie(u'变形金刚')  
  19.     print "all over %s" % ctime()  

python多线程

Python中使用线程有两种方式:函数 或者用 类来包装线程对象。

[plain]  view plain  copy
  1. 函数式  :调用thread模块中的start_new_thread()函数来产生新线程。  
  2. 语法如下: thread.start_new_thread(function, args[, kwargs])  
  3. 参数说明:  
  4.     function : 线程函数。  
  5.     args     : 传递给线程函数的参数,他必须是个tuple类型。  
  6.     kwargs   : 可选参数。  
[python]  view plain  copy
  1. import thread  
  2. import time  
  3.   
  4. def print_time(thread_name, delay):  
  5.         count = 0  
  6.         while count < 5:  
  7.                 time.sleep(delay)  
  8.                 count += 1  
  9.                 print "%s: %s" % (thread_name, time.ctime(time.time()))  
  10.   
  11.   
  12. if __name__ == "__main__":  
  13.     try:  
  14.             thread.start_new_thread(print_time, ("Thread-1"2))  
  15.             thread.start_new_thread(print_time, ("Thread-2"4))  
  16.     except BaseException as e:  
  17.             print e  
  18.             print "Error: unable to start thread"  
  19.     while 1:  
  20.             pass  

使用Threading模块创建线程,直接从threading.Thread继承,然后重写__init__方法和run方法:

[python]  view plain  copy
  1. # coding=utf-8  
  2. # !/usr/bin/python  
  3. import threading  
  4. import time  
  5.   
  6. exitFlag = 0  
  7.   
  8. class myThread(threading.Thread):  
  9.     def __init__(self, threadID, name, counter):  
  10.         threading.Thread.__init__(self)  
  11.         self.threadID = threadID  
  12.         self.name = name  
  13.         self.counter = counter  
  14.   
  15.     def run(self):  
  16.         print "Starting " + self.name  
  17.         print_time(self.name, self.counter, 5)  
  18.         print "Exiting " + self.name  
  19.   
  20.   
  21. def print_time(threadName, delay, counter):  
  22.     while counter:  
  23.         if exitFlag:  
  24.             thread.exit()  
  25.         time.sleep(delay)  
  26.         print "%s: %s" % (threadName, time.ctime(time.time()))  
  27.         counter -= 1  
  28.   
  29. thread1 = myThread(1"Thread-1"1)  
  30. thread2 = myThread(2"Thread-2"2)  
  31.   
  32. thread1.start()  
  33. thread2.start()  
  34. print "Exiting Main Thread"  

python提供了两个模块来实现多线程thread 和threading 。 thread有一些缺点,在threading 得到了弥补,强烈建议直接使用threading。

[python]  view plain  copy
  1. # coding=utf-8  
  2. import threading  
  3. from time import ctime, sleep  
  4.   
  5.   
  6. def music(argv):  
  7.     for i in range(2):  
  8.         print "listen music  %s. %s" % (argv, ctime())  
  9.         sleep(1)  
  10.   
  11.   
  12. def movie(argv):  
  13.     for i in range(2):  
  14.         print "watch movie  %s! %s" % (argv, ctime())  
  15.         sleep(5)  
  16.   
  17.   
  18. threads = []  
  19. t1 = threading.Thread(target=music, args=(u'trouble is a friend',))  
  20. threads.append(t1)  
  21. t2 = threading.Thread(target=movie, args=(u'变形金刚',))  
  22. threads.append(t2)  
  23.   
  24. if __name__ == '__main__':  
  25.     for t in threads:  
  26.         t.setDaemon(True)  
  27.         t.start()  
  28.     print "all over %s" % ctime()  

setDaemon(True) 将线程声明为守护线程,必须在start() 方法调用之前设置,如果不设置为守护线程程序会被无限挂起。
子线程启动后,父线程也继续执行下去,当父线程执行完最后一条语句print "all over %s" %ctime()后,没有等待子线程,直接就退出了,同时子线程也一同结束。
start()开始线程活动。

[python]  view plain  copy
  1. # 调整程序:  
  2. if __name__ == '__main__':  
  3.     for t in threads:  
  4.         t.setDaemon(True)  
  5.         t.start()  
  6.       
  7.     t.join()  
  8.     print "all over %s" %ctime()  

对上面的程序加了个join()方法,用于等待线程终止。join()的作用是,在子线程完成运行之前,这个子线程的父线程将一直被阻塞。
注意: join()方法的位置是在for循环外的,也就是说必须等待for循环里的两个进程都结束后,才去执行主进程。

[python]  view plain  copy
  1. import threading  
  2. import time  
  3.   
  4.   
  5. def worker(num):  
  6.     time.sleep(1)  
  7.     print("The num is  %d" % num)  
  8.     print t.getName()  
  9.     return  
  10.   
  11. for i in range(20):  
  12.     t = threading.Thread(target=worker, args=(i,), name="testThread")  
  13.     t.start()  

Thread方法说明

[plain]  view plain  copy
  1. t.start()       激活线程,     
  2. t.getName()     获取线程的名称     
  3. t.setName()     设置线程的名称     
  4. t.name          获取或设置线程的名称     
  5. t.is_alive()    判断线程是否为激活状态     
  6. t.isAlive()     判断线程是否为激活状态     
  7. t.setDaemon()   设置为后台线程或前台线程(默认:False);通过一个布尔值设置线程是否为守护线程,必须在执行start()方法之后才可以使用。  
  8.                 如果是后台线程,主线程执行过程中,后台线程也在进行,主线程执行完毕后,后台线程不论成功与否,均停止;  
  9.                 如果是前台线程,主线程执行过程中,前台线程也在进行,主线程执行完毕后,等待前台线程也执行完成后,程序停止     
  10. t.isDaemon()    判断是否为守护线程     
  11. t.ident         获取线程的标识符。线程标识符是一个非零整数,只有在调用了start()方法之后该属性才有效,否则它只返回None。     
  12. t.join()        逐个执行每个线程,执行完毕后继续往下执行,该方法使得多线程变得无意义     
  13. t.run()         线程被cpu调度后自动执行线程对象的run方法  

线程同步


如果多个线程共同对某个数据修改,则可能出现不可预料的结果,为了保证数据的正确性,需要对多个线程进行同步。
使用Thread对象的Lock和Rlock可以实现简单的线程同步,这两个对象都有acquire方法和release方法。
对于那些需要每次只允许一个线程操作的数据,可以将其操作放到acquire和release方法之间。
如下:
多线程的优势在于可以同时运行多个任务(至少感觉起来是这样)。但是当线程需要共享数据时,可能存在数据不同步的问题。
考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。
那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。
为了避免这种情况,引入了锁的概念。锁有两种状态:锁定 和 未锁定。
每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,
也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。
经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。

[python]  view plain  copy
  1. # coding=utf-8  
  2. # !/usr/bin/python  
  3. import threading  
  4. import time  
  5.   
  6.   
  7. class myThread(threading.Thread):  
  8.     def __init__(self, threadID, name, counter):  
  9.         threading.Thread.__init__(self)  
  10.         self.threadID = threadID  
  11.         self.name = name  
  12.         self.counter = counter  
  13.   
  14.     def run(self):  
  15.         print "Starting " + self.name  
  16.         # 获得锁,成功获得锁定后返回True  
  17.         # 可选的timeout参数不填时将一直阻塞直到获得锁定  
  18.         # 否则超时后将返回False  
  19.         threadLock.acquire()  
  20.         print_time(self.name, self.counter, 3)  
  21.         # 释放锁  
  22.         threadLock.release()  
  23.   
  24.   
  25. def print_time(threadName, delay, counter):  
  26.     while counter:  
  27.         time.sleep(delay)  
  28.         print "%s: %s" % (threadName, time.ctime(time.time()))  
  29.         counter -= 1  
  30.   
  31.   
  32. threadLock = threading.Lock()  
  33. threads = []  
  34. # 创建新线程  
  35. thread1 = myThread(1"Thread-1"1)  
  36. thread2 = myThread(2"Thread-2"2)  
  37. # 开启新线程  
  38. thread1.start()  
  39. thread2.start()  
  40. # 添加线程到线程列表中  
  41. threads.append(thread1)  
  42. threads.append(thread2)  
  43. # 等待所有线程完成  
  44. for t in threads:  
  45.     t.join()  
  46. print "Exiting Main Thread"  

线程优先级队列 (Queue)

Python的Queue模块中提供了同步的、线程安全的队列类。
包括FIFO(先入先出)队列Queue,LIFO(后入先出)队列LifoQueue,和优先级队列PriorityQueue。
这些队列都实现了锁原语,能够在多线程中直接使用。可以使用队列来实现线程间的同步。

Queue模块中的常用方法:

[python]  view plain  copy
  1. Queue.qsize()    返回队列的大小  
  2. Queue.empty()    如果队列为空,返回True,反之False  
  3. Queue.full()     如果队列满了,返回True,反之False  
  4. Queue.full 与 maxsize 大小对应  
  5. Queue.get([block[, timeout]]) 获取队列,timeout是等待时间  
  6. Queue.get_nowait()            相当Queue.get(False)  
  7. Queue.put(item)               写入队列,timeout是等待时间  
  8. Queue.put_nowait(item)        相当Queue.put(item, False)  
  9. Queue.task_done()             在完成一项工作之后,Queue.task_done()函数向任务已经完成的队列发送一个信号  
  10. Queue.join()                  实际上意味着等到队列为空,再执行别的操作  
[python]  view plain  copy
  1. # coding=utf-8  
  2. # !/usr/bin/python  
  3. import Queue  
  4. import threading  
  5. import time  
  6.   
  7. exitFlag = 0  
  8.   
  9.   
  10. class myThread(threading.Thread):  
  11.     def __init__(self, threadID, name, q):  
  12.         threading.Thread.__init__(self)  
  13.         self.threadID = threadID  
  14.         self.name = name  
  15.         self.q = q  
  16.   
  17.     def run(self):  
  18.         print "Starting " + self.name  
  19.         process_data(self.name, self.q)  
  20.         print "Exiting " + self.name  
  21.   
  22.   
  23. def process_data(threadName, q):  
  24.     while not exitFlag:  
  25.         queueLock.acquire()  
  26.         if not workQueue.empty():  
  27.             data = q.get()  
  28.             queueLock.release()  
  29.             print "%s processing %s" % (threadName, data)  
  30.         else:  
  31.             queueLock.release()  
  32.         time.sleep(1)  
  33.   
  34.   
  35. threadList = ["Thread-1""Thread-2""Thread-3"]  
  36. nameList = ["One""Two""Three""Four""Five"]  
  37. queueLock = threading.Lock()  
  38. workQueue = Queue.Queue(10)  
  39. threads = []  
  40. threadID = 1  
  41.   
  42. # 创建线程  
  43. for tName in threadList:  
  44.     thread = myThread(threadID, tName, workQueue)  
  45.     thread.start()  
  46.     threads.append(thread)  
  47.     threadID += 1  
  48.   
  49. # 填充队列  
  50. queueLock.acquire()  
  51. for word in nameList:  
  52.     workQueue.put(word)  
  53. queueLock.release()  
  54.   
  55. # 等待队列清空  
  56. while not workQueue.empty():  
  57.     pass  
  58. # 通知线程退出  
  59. exitFlag = 1  
  60. # 等待所有线程完成  
  61. for t in threads:  
  62.     t.join()  
  63. print "Exiting Main Thread"  

线程锁 threading.RLock 和 threading.Lock


由于线程之间是进行随机调度,并且每个线程可能只执行n条执行之后,CPU接着执行其他线程。为了保证数据的准确性,引入了锁的概念。

所以,可能出现如下问题:假设列表A的所有元素就为0,当一个线程从前向后打印列表的所有元素,另外一个线程则从后向前修改列表的元素为1, 那么输出的时候,列表的元素就会一部分为0,一部分为1,这就导致了数据的不一致。锁的出现解决了这个问题。

[python]  view plain  copy
  1. import threading  
  2. import time  
  3. globals_num = 0  
  4. lock = threading.RLock()  
  5.   
  6.   
  7. def func():  
  8.     lock.acquire()  # 获得锁  
  9.     global globals_num  
  10.     globals_num += 1  
  11.     time.sleep(1)  
  12.     print(globals_num)  
  13.     lock.release()  # 释放锁  
  14.   
  15. for i in range(10):  
  16.     t = threading.Thread(target=func)  
  17.     t.start()  
  18.     pass  

threading.RLock 和 threading.Lock 的区别


RLock允许在同一线程中被多次acquire。而Lock却不允许这种情况。如果使用RLock,那么acquire和release必须成对出现,即调用了n次acquire,必须调用n次的release才能真正释放所占用的琐。

[python]  view plain  copy
  1. import threading    
  2. lock = threading.Lock()    #Lock对象   
  3. lock.acquire()    
  4. lock.acquire()  #产生了死琐。   
  5. lock.release()   
  6. lock.release()    
[python]  view plain  copy
  1. import threading    
  2. rLock = threading.RLock()  #RLock对象   
  3. rLock.acquire()    
  4. rLock.acquire()    #在同一线程内,程序不会堵塞。   
  5. rLock.release()   
  6. rLock.release()  

threading.Event

python线程的事件用于主线程控制其他线程的执行。事件主要提供了三个方法 set、wait、clear。 事件处理的机制:全局定义了一个“Flag”,如果“Flag”值为 False,那么当程序执行event.wait方法时就会阻塞,如果“Flag”值为True,那么event.wait方法时便不再阻塞。
clear:               将“Flag”设置为False 
set:                   将“Flag”设置为True 
Event.isSet() :判断标识位是否为Ture。

[python]  view plain  copy
  1. import threading  
  2.   
  3. def do(event):  
  4.     print('start')  
  5.     event.wait()  
  6.     print('execute')  
  7.   
  8. event_obj = threading.Event()  
  9. for i in range(10):  
  10.     t = threading.Thread(target=do, args=(event_obj,))  
  11.     t.start()  
  12.   
  13. event_obj.clear()  
  14. # inp = input('input:')  
  15. inp = raw_input('input:')  
  16. if inp == 'true':  
  17.     event_obj.set()  
当线程执行的时候,如果flag为False,则线程会阻塞,当flag为True的时候,线程不会阻塞。它提供了本地和远程的并发性。

threading.Condition

一个condition变量总是与某些类型的锁相联系,这个可以使用默认的情况或创建一个,
当几个condition变量必须共享和同一个锁的时候,是很有用的。锁是conditon对象的一部分:没有必要分别跟踪。

condition变量服从上下文管理协议:with语句块封闭之前可以获取与锁的联系。 
acquire() 和 release() 会调用与锁相关联的相应的方法。 
其他和锁关联的方法必须被调用,wait()方法会释放锁,
当另外一个线程使用 notify() or notify_all()唤醒它之前会一直阻塞。一旦被唤醒,wait()会重新获得锁并返回,

Condition类实现了一个conditon变量。这个conditiaon变量允许一个或多个线程等待,直到他们被另一个线程通知。
如果lock参数,被给定一个非空的值,,那么他必须是一个lock或者Rlock对象,它用来做底层锁。否则,会创建一个新的Rlock对象,用来做底层锁。

wait(timeout=None) :等待通知,或者等到设定的超时时间。
当调用这wait()方法时,如果调用它的线程没有得到锁,那么会抛出一个RuntimeError异常。
wati()释放锁以后,在被调用相同条件的另一个进程用notify() or notify_all() 叫醒之前会一直阻塞。
wait()还可以指定一个超时时间。 如果有等待的线程,notify()方法会唤醒一个在等待conditon变量的线程。notify_all() 则会唤醒所有在等待conditon变量的线程。

注意: notify()和notify_all()不会释放锁,也就是说,线程被唤醒后不会立刻返回他们的wait() 调用。
除非线程调用notify()和notify_all()之后放弃了锁的所有权。 
在典型的设计风格里,利用condition变量用锁去通许访问一些共享状态,线程在获取到它想得到的状态前,会反复调用wait()。
修改状态的线程在他们状态改变时调用 notify() or notify_all(),用这种方式,线程会尽可能的获取到想要的一个等待者状态。

例子:生产者-消费者模型

[python]  view plain  copy
  1. import threading  
  2. import time  
  3.   
  4. def consumer(cond):  
  5.     with cond:  
  6.         print("consumer before wait")  
  7.         cond.wait()  
  8.         print("consumer after wait")  
  9.   
  10. def producer(cond):  
  11.     with cond:  
  12.         print("producer before notifyAll")  
  13.         cond.notifyAll()  
  14.         print("producer after notifyAll")  
  15.   
  16. condition = threading.Condition()  
  17. c1 = threading.Thread(name="c1", target=consumer, args=(condition,))  
  18. c2 = threading.Thread(name="c2", target=consumer, args=(condition,))  
  19. p = threading.Thread(name="p", target=producer, args=(condition,))  
  20.   
  21. c1.start()  
  22. time.sleep(2)  
  23. c2.start()  
  24. time.sleep(2)  
  25. p.start()  

python 多进程共享变量

https://my.oschina.net/leejun2005/blog/203148

共享内存 (Shared memory)

Data can be stored in a shared memory map using Value or Array. 

For example, the following code.   https://docs.python.org/2/library/multiprocessing.html#sharing-state-between-processes

在使用并发设计的时候最好尽可能的避免共享数据,尤其是在使用多进程的时候。如果你真有需要要共享数据, multiprocessing提供了两种方式。 

multiprocessing 中的 Array 和 Value。数据可以用 Value 或 Array 存储在一个共享内存地图里,如下:

[python]  view plain  copy
  1. from multiprocessing import Array, Value, Process  
  2.   
  3. def func(a, b):  
  4.     a.value = 3.333333333333333  
  5.     for j in range(len(b)):  
  6.         b[j] = -b[j]  
  7.   
  8. if __name__ == "__main__":  
  9.     num = Value('d'0.0)  
  10.     arr = Array('i', range(11))  
  11.   
  12.     if 0:  
  13.         t = Process(target=func, args=(num, arr))  
  14.         t.start()  
  15.         t.join()  
  16.     else:  
  17.         c = Process(target=func, args=(num, arr))  
  18.         d = Process(target=func, args=(num, arr))  
  19.         c.start()  
  20.         d.start()  
  21.         c.join()  
  22.         d.join()  
  23.   
  24.     print(num.value)  
  25.     print(arr[:])  
  26.     for i in arr:  
  27.         print i,  
输出
[plain]  view plain  copy
  1. 3.33333333333  
  2. 0 1 2 3 4 5 6 7 8 9 10  

创建 num 和 arr 时,“d”和“i”参数 由Array模块使用的typecodes创建:“d”表示一个双精度的浮点数,“i”表示一个有符号的整数,这些共享对象将被线程安全的处理。

[plain]  view plain  copy
  1. Array(‘i’, range(10))中的‘i’参数:     
  2. ‘c’: ctypes.c_char      
  3. ‘u’: ctypes.c_wchar      
  4. ‘b’: ctypes.c_byte      
  5. ‘B’: ctypes.c_ubyte  
  6. ‘h’: ctypes.c_short      
  7. ‘H’: ctypes.c_ushort     
  8. ‘i’: ctypes.c_int       
  9. ‘I’: ctypes.c_uint    
  10. ‘l’: ctypes.c_long,      
  11. ‘L’: ctypes.c_ulong      
  12. ‘f’: ctypes.c_float      
  13. ‘d’: ctypes.c_double  

Server process

A manager object returned by Manager() controls a server process which holds Python objects and allows other processes to manipulate them using proxies.
A manager returned by Manager() will support types list, dict, Namespace, Lock, RLock, Semaphore, BoundedSemaphore, Condition, Event, Queue, Value and Array.

https://docs.python.org/2/library/multiprocessing.html#managers

multiprocessing 中的 Manager()

Python中进程间共享数据,除了基本的queue,pipe和value+array外,还提供了更高层次的封装。使用multiprocessing.Manager可以简单地使用这些高级接口。
Manager()返回的manager对象控制了一个server进程,此进程包含的python对象可以被其他的进程通过proxies来访问。从而达到多进程间数据通信且安全。
Manager支持的类型有list,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Queue,Value和Array。

[python]  view plain  copy
  1. from multiprocessing import Process, Manager  
  2.   
  3. def f(d, l):  
  4.     d["name"] = "king"  
  5.     d["age"] = 100  
  6.     d["Job"] = "python"  
  7.     l.reverse()  
  8.   
  9. if __name__ == "__main__":  
  10.     with Manager() as man:  
  11.         d_temp = man.dict()  
  12.         l_temp = man.list(range(10))  
  13.   
  14.         p = Process(target=f, args=(d_temp, l_temp))  
  15.         p.start()  
  16.         p.join()  
  17.   
  18.         print(d_temp)  
  19.         print(l_temp)   
Server process manager 比 shared memory 更灵活,因为它可以支持任意的对象类型。另外,一个单独的manager可以通过进程在网络上不同的计算机之间共享,不过他比shared memory要慢。


python 协程

关于协程,可以参考 greenlet,stackless,gevent,eventlet等的实现。

我们都知道并发(不是并行)编程目前有四种方式,多进程,多线程,异步,和协程。

多进程编程在python中有类似C的os.fork,当然还有更高层封装的multiprocessing标准库,在之前写过的python高可用程序设计方法http://www.cnblogs.com/hymenz/p/3488837.html中提供了类似nginx中master process和worker process间信号处理的方式,保证了业务进程的退出可以被主进程感知。

多线程编程python中有Thread和threading,在linux下所谓的线程,实际上是LWP轻量级进程,其在内核中具有和进程相同的调度方式,有关LWP,COW(写时拷贝),fork,vfork,clone等的资料较多,这里不再赘述。

异步在linux下主要有三种实现select,poll,epoll 。

说 python 的 协程 肯定要说yield

[python]  view plain  copy
  1. #coding=utf-8  
  2. import time  
  3. import sys  
  4. # 生产者  
  5. def produce(l):  
  6.     i=0  
  7.     while 1:  
  8.         if i < 5:  
  9.             l.append(i)  
  10.             yield i  
  11.             i=i+1  
  12.             time.sleep(1)  
  13.         else:  
  14.             return  
  15.         
  16. # 消费者  
  17. def consume(l):  
  18.     p = produce(l)  
  19.     while 1:  
  20.         try:  
  21.             p.next()  
  22.             while len(l) > 0:  
  23.                 print l.pop()  
  24.         except StopIteration:  
  25.             sys.exit(0)  
  26. l = []  
  27. consume(l)  

在上面的例子中,当程序执行到produce的yield i时,返回了一个generator,当我们在custom中调用p.next(),程序又返回到produce的yield i继续执行,这样l中又append了元素,然后我们print l.pop(),直到p.next()引发了StopIteration异常。

通过上面的例子我们看到协程的调度对于内核来说是不可见的,协程间是协同调度的,这使得并发量在上万的时候,协程的性能是远高于线程的。

[python]  view plain  copy
  1. import stackless  
  2. import urllib2  
  3. def output():  
  4.     while 1:  
  5.         url=chan.receive()  
  6.         print url  
  7.         f=urllib2.urlopen(url)  
  8.         #print f.read()  
  9.         print stackless.getcurrent()  
  10.        
  11. def input():  
  12.     f=open('url.txt')  
  13.     l=f.readlines()  
  14.     for i in l:  
  15.         chan.send(i)  
  16. chan=stackless.channel()  
  17. [stackless.tasklet(output)() for i in xrange(10)]  
  18. stackless.tasklet(input)()  
  19. stackless.run()  

协程的好处:

  • 无需线程上下文切换的开销
  • 无需原子操作锁定及同步的开销
  • 方便切换控制流,简化编程模型
  • 高并发+高扩展性+低成本:一个CPU支持上万的协程都不是问题。所以很适合用于高并发处理。

缺点:

  • 无法利用多核资源:协程的本质是个单线程,它不能同时将 单个CPU 的多个核用上,协程需要和进程配合才能运行在多CPU上.当然我们日常所编写的绝大部分应用都没有这个必要,除非是cpu密集型应用。
  • 进行阻塞(Blocking)操作(如IO时)会阻塞掉整个程序


[plain]  view plain  copy
  1. 协程,又称微线程,纤程。英文名Coroutine。  
  2.   
  3. 子程序,或者称为函数,在所有语言中都是层级调用,比如A调用B,B在执行过程中又调用了C,C执行完毕返回,B执行完毕返回,最后是A执行完毕。  
  4. 所以子程序调用是通过栈实现的,一个线程就是执行一个子程序。  
  5.   
  6. 子程序调用总是一个入口,一次返回,调用顺序是明确的。  
  7.   
  8. 而协程的调用和子程序不同。  
  9.   
  10. 协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。  
  11.   
  12. 注意,在一个子程序中中断,去执行其他子程序,不是函数调用,有点类似CPU的中断。比如子程序A、B:  
  13.   
  14. def A():  
  15.     print '1'  
  16.     print '2'  
  17.     print '3'  
  18.   
  19. def B():  
  20.     print 'x'  
  21.     print 'y'  
  22.     print 'z'  
  23.       
  24. 假设由协程执行,在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A,结果可能是:  
  25.   
  26. 1  
  27. 2  
  28. x  
  29. y  
  30. 3  
  31. z  
  32. 但是在A中是没有调用B的,所以协程的调用比函数调用理解起来要难一些。  
  33.   
  34. 看起来A、B的执行有点像多线程,  
  35. 但协程的特点在于是一个线程执行,  
  36.   
  37. 那和多线程比,协程有何优势?  
  38. 最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,  
  39. 因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。  
  40.   
  41. 第二大优势就是不需要多线程的锁机制。  
  42. 因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。  
  43.   
  44.   
  45. 因为协程是一个线程执行,那怎么利用多核CPU呢?  
  46. 最简单的方法是多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。  
  47.   
  48. Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。  
  49. 虽然支持不完全,但已经可以发挥相当大的威力了。  

一个例子:

传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过yield跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

[python]  view plain  copy
  1. import time  
  2.   
  3. def consumer():  
  4.     r = ''  
  5.     while True:  
  6.         n = yield r  
  7.         if not n:  
  8.             return  
  9.         print('[CONSUMER] Consuming %s...' % n)  
  10.         time.sleep(1)  
  11.         r = '200 OK'  
  12.   
  13. def produce(c):  
  14.     c.next()  
  15.     n = 0  
  16.     while n < 5:  
  17.         n = n + 1  
  18.         print('[PRODUCER] Producing %s...' % n)  
  19.         r = c.send(n)  
  20.         print('[PRODUCER] Consumer return: %s' % r)  
  21.     c.close()  
  22.   
  23. if __name__=='__main__':  
  24.     c = consumer()  
  25.     produce(c)  

执行结果:

[plain]  view plain  copy
  1. [PRODUCER] Producing 1...  
  2. [CONSUMER] Consuming 1...  
  3. [PRODUCER] Consumer return: 200 OK  
  4. [PRODUCER] Producing 2...  
  5. [CONSUMER] Consuming 2...  
  6. [PRODUCER] Consumer return: 200 OK  
  7. [PRODUCER] Producing 3...  
  8. [CONSUMER] Consuming 3...  
  9. [PRODUCER] Consumer return: 200 OK  
  10. [PRODUCER] Producing 4...  
  11. [CONSUMER] Consuming 4...  
  12. [PRODUCER] Consumer return: 200 OK  
  13. [PRODUCER] Producing 5...  
  14. [CONSUMER] Consuming 5...  
  15. [PRODUCER] Consumer return: 200 OK  

注意到consumer函数是一个generator(生成器),把一个consumer传入produce后:

        1. 首先调用c.next()启动生成器;
        2. 然后,一旦生产了东西,通过c.send(n)切换到consumer执行;
        3. consumer通过yield拿到消息,处理,又通过yield把结果传回;
        4. produce拿到consumer处理的结果,继续生产下一条消息;
        5. produce决定不生产了,通过c.close()关闭consumer,整个过程结束。

整个流程无锁,由一个线程执行,produce和consumer协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

最后套用Donald Knuth的一句话总结协程的特点:“子程序就是协程的一种特例


线程和进程的操作是由程序触发系统接口,最后的执行者是系统;协程的操作则是程序员。
协程存在的意义:对于多线程应用,CPU通过切片的方式来切换线程间的执行,线程切换时需要耗时(保存状态,下次继续)。
协程,则只使用一个线程,在一个线程中规定某个代码块执行顺序。 
协程的适用场景:当程序中存在大量不需要CPU的操作时(IO),
event loop是协程执行的控制点,如果你希望执行协程,就需要用到它们。
event loop提供了如下的特性: 

                 注册、执行、取消延时调用(异步函数)、创建用于通信的client和server协议(工具)、创建和别的程序通信的子进程和协议(工具) 把函数调用送入线程池中

协程示例:

[plain]  view plain  copy
  1. #---------python3_start---------------  
  2. import asyncio  
  3. async def cor1():  
  4.     print("COR1 start")  
  5.     await cor2()  
  6.     print("COR1 end")  
  7.   
  8.   
  9. async def cor2():  
  10.     print("COR2")  
  11.   
  12. loop = asyncio.get_event_loop()  
  13. loop.run_until_complete(cor1())  
  14. loop.close()  
  15. #---------python3_end---------------  

最后三行是重点。
        asyncio.get_event_loop() : asyncio启动默认的event loop 
        run_until_complete() :         这个函数是阻塞执行的,知道所有的异步函数执行完成, 
        close() :                                 关闭 event loop。

python 的 greenlet 模块

[python]  view plain  copy
  1. import greenlet  
  2. def fun1():  
  3.     print("12")  
  4.     gr2.switch()  
  5.     print("56")  
  6.     gr2.switch()  
  7.   
  8. def fun2():  
  9.     print("34")  
  10.     gr1.switch()  
  11.     print("78")  
  12.   
  13. gr1 = greenlet.greenlet(fun1)  
  14. gr2 = greenlet.greenlet(fun2)  
  15. gr1.switch()  

gevent

gevent属于第三方模块需要下载安装包
pip3 install --upgrade pip3 
pip3 install gevent 

[python]  view plain  copy
  1. import gevent  
  2. def fun1():  
  3.     print("www.baidu.com")  # 第一步  
  4.     gevent.sleep(0)  
  5.     print("end the baidu.com")  # 第三步  
  6.   
  7. def fun2():  
  8.     print("www.zhihu.com")  # 第二步  
  9.     gevent.sleep(0)  
  10.     print("end th zhihu.com")  # 第四步  
  11.   
  12. gevent.joinall([  
  13.     gevent.spawn(fun1),  
  14.     gevent.spawn(fun2),  
  15. ])  

遇到IO操作自动切换:

[python]  view plain  copy
  1. import gevent  
  2. import requests  
  3.   
  4. def func(url):  
  5.     print("get: %s" % url)  
  6.     gevent.sleep(0)  
  7.     proxies = {  
  8.         "http""http://172.17.18.80:8080",  
  9.         "https""http://172.17.18.80:8080",  
  10.     }  
  11.   
  12.     date = requests.get(url, proxies=proxies)  
  13.     ret = date.text  
  14.     print(url, len(ret))  
  15.   
  16. gevent.joinall([  
  17.     gevent.spawn(func, 'https://www.baidu.com/'),  
  18.     gevent.spawn(func, 'http://www.sina.com.cn/'),  
  19.     gevent.spawn(func, 'http://www.qq.com/'),  
  20. ])  
python中多进程+协程的使用以及为什么要用它:  http://blog.csdn.net/lambert310/article/details/51162634
从两个简单例子窥视协程的惊人性能(Python): http://walkerqt.blog.51cto.com/1310630/1439034

greenlet:http://greenlet.readthedocs.org/en/latest/
eventlet: http://eventlet.net/
http://gashero.iteye.com/blog/442177



mutilprocess简介

像线程一样管理进程,这个是mutilprocess的核心,他与threading很是相像,对多核CPU的利用率会比threading好的多。

简单的创建进程

[python]  view plain  copy
  1. import multiprocessing  
  2.   
  3. def worker(num):  
  4.     """thread worker function"""  
  5.     print 'Worker:', num  
  6.     return  
  7.   
  8. if __name__ == '__main__':  
  9.     jobs = []  
  10.     for i in range(5):  
  11.         p = multiprocessing.Process(target=worker, args=(i,))  
  12.         jobs.append(p)  
  13.         p.start()  

确定当前的进程,即是给进程命名,方便标识区分,跟踪

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3.   
  4. def worker():  
  5.     name = multiprocessing.current_process().name  
  6.     print name, 'Starting'  
  7.     time.sleep(2)  
  8.     print name, 'Exiting'  
  9.   
  10. def my_service():  
  11.     name = multiprocessing.current_process().name  
  12.     print name, 'Starting'  
  13.     time.sleep(3)  
  14.     print name, 'Exiting'  
  15.   
  16. if __name__ == '__main__':  
  17.     service = multiprocessing.Process(name='my_service',  
  18.                                       target=my_service)  
  19.     worker_1 = multiprocessing.Process(name='worker 1',  
  20.                                        target=worker)  
  21.     worker_2 = multiprocessing.Process(target=worker) # default name  
  22.   
  23.     worker_1.start()  
  24.     worker_2.start()  
  25.     service.start()  

守护进程

守护进程就是不阻挡主程序退出,自己干自己的。 mutilprocess.setDaemon(True)就这句。

等待守护进程退出,要加上join,join可以传入浮点数值,等待n久就不等了

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3. import sys  
  4.   
  5. def daemon():  
  6.     name = multiprocessing.current_process().name  
  7.     print 'Starting:', name  
  8.     time.sleep(2)  
  9.     print 'Exiting :', name  
  10.   
  11. def non_daemon():  
  12.     name = multiprocessing.current_process().name  
  13.     print 'Starting:', name  
  14.     print 'Exiting :', name  
  15.   
  16. if __name__ == '__main__':  
  17.     d = multiprocessing.Process(name='daemon',  
  18.                                 target=daemon)  
  19.     d.daemon = True  
  20.   
  21.     n = multiprocessing.Process(name='non-daemon',  
  22.                                 target=non_daemon)  
  23.     n.daemon = False  
  24.   
  25.     d.start()  
  26.     n.start()  
  27.   
  28.     d.join(1)  
  29.     print 'd.is_alive()', d.is_alive()  
  30.     n.join()  

终止进程

最好使用 poison pill,强制的使用terminate()。注意 terminate之后要join,使其可以更新状态

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3.   
  4. def slow_worker():  
  5.     print 'Starting worker'  
  6.     time.sleep(0.1)  
  7.     print 'Finished worker'  
  8.   
  9. if __name__ == '__main__':  
  10.     p = multiprocessing.Process(target=slow_worker)  
  11.     print 'BEFORE:', p, p.is_alive()  
  12.   
  13.     p.start()  
  14.     print 'DURING:', p, p.is_alive()  
  15.   
  16.     p.terminate()  
  17.     print 'TERMINATED:', p, p.is_alive()  
  18.   
  19.     p.join()  
  20.     print 'JOINED:', p, p.is_alive()  

进程的退出状态

  1.  == 0     未生成任何错误
  2.  0           进程有一个错误,并以该错误码退出
  3.  < 0       进程由一个-1 * exitcode信号结束
[python]  view plain  copy
  1. import multiprocessing  
  2. import sys  
  3. import time  
  4.   
  5. def exit_error():  
  6.     sys.exit(1)  
  7.   
  8. def exit_ok():  
  9.     return  
  10.   
  11. def return_value():  
  12.     return 1  
  13.   
  14. def raises():  
  15.     raise RuntimeError('There was an error!')  
  16.   
  17. def terminated():  
  18.     time.sleep(3)  
  19.   
  20. if __name__ == '__main__':  
  21.     jobs = []  
  22.     for f in [exit_error, exit_ok, return_value, raises, terminated]:  
  23.         print 'Starting process for', f.func_name  
  24.         j = multiprocessing.Process(target=f, name=f.func_name)  
  25.         jobs.append(j)  
  26.         j.start()  
  27.   
  28.     jobs[-1].terminate()  
  29.   
  30.     for j in jobs:  
  31.         j.join()  
  32.         print '%15s.exitcode = %s' % (j.name, j.exitcode)  

日志

方便的调试,可以用logging

[python]  view plain  copy
  1. import multiprocessing  
  2. import logging  
  3. import sys  
  4.   
  5. def worker():  
  6.     print 'Doing some work'  
  7.     sys.stdout.flush()  
  8.   
  9. if __name__ == '__main__':  
  10.     multiprocessing.log_to_stderr()  
  11.     logger = multiprocessing.get_logger()  
  12.     logger.setLevel(logging.INFO)  
  13.     p = multiprocessing.Process(target=worker)  
  14.     p.start()  
  15.     p.join()  

派生进程

利用class来创建进程,定制子类

[python]  view plain  copy
  1. import multiprocessing  
  2.   
  3. class Worker(multiprocessing.Process):  
  4.   
  5.     def run(self):  
  6.         print 'In %s' % self.name  
  7.         return  
  8.   
  9. if __name__ == '__main__':  
  10.     jobs = []  
  11.     for i in range(5):  
  12.         p = Worker()  
  13.         jobs.append(p)  
  14.         p.start()  
  15.     for j in jobs:  
  16.         j.join()  

python进程间传递消息

这一块我之前结合SocketServer写过一点,见Python多进程

一般的情况是Queue来传递。

[python]  view plain  copy
  1. import multiprocessing  
  2.   
  3. class MyFancyClass(object):  
  4.   
  5.     def __init__(self, name):  
  6.         self.name = name  
  7.   
  8.     def do_something(self):  
  9.         proc_name = multiprocessing.current_process().name  
  10.         print 'Doing something fancy in %s for %s!' % \  
  11.             (proc_name, self.name)  
  12.   
  13. def worker(q):  
  14.     obj = q.get()  
  15.     obj.do_something()  
  16.   
  17. if __name__ == '__main__':  
  18.     queue = multiprocessing.Queue()  
  19.   
  20.     p = multiprocessing.Process(target=worker, args=(queue,))  
  21.     p.start()  
  22.   
  23.     queue.put(MyFancyClass('Fancy Dan'))  
  24.   
  25.     # Wait for the worker to finish  
  26.     queue.close()  
  27.     queue.join_thread()  
  28.     p.join()  
  29.   
  30. import multiprocessing  
  31. import time  
  32.   
  33. class Consumer(multiprocessing.Process):  
  34.   
  35.     def __init__(self, task_queue, result_queue):  
  36.         multiprocessing.Process.__init__(self)  
  37.         self.task_queue = task_queue  
  38.         self.result_queue = result_queue  
  39.   
  40.     def run(self):  
  41.         proc_name = self.name  
  42.         while True:  
  43.             next_task = self.task_queue.get()  
  44.             if next_task is None:  
  45.                 # Poison pill means shutdown  
  46.                 print '%s: Exiting' % proc_name  
  47.                 self.task_queue.task_done()  
  48.                 break  
  49.             print '%s: %s' % (proc_name, next_task)  
  50.             answer = next_task()  
  51.             self.task_queue.task_done()  
  52.             self.result_queue.put(answer)  
  53.         return  
  54.   
  55. class Task(object):  
  56.     def __init__(self, a, b):  
  57.         self.a = a  
  58.         self.b = b  
  59.     def __call__(self):  
  60.         time.sleep(0.1# pretend to take some time to do the work  
  61.         return '%s * %s = %s' % (self.a, self.b, self.a * self.b)  
  62.     def __str__(self):  
  63.         return '%s * %s' % (self.a, self.b)  
  64.   
  65. if __name__ == '__main__':  
  66.     # Establish communication queues  
  67.     tasks = multiprocessing.JoinableQueue()  
  68.     results = multiprocessing.Queue()  
  69.   
  70.     # Start consumers  
  71.     num_consumers = multiprocessing.cpu_count() * 2  
  72.     print 'Creating %d consumers' % num_consumers  
  73.     consumers = [ Consumer(tasks, results)  
  74.                   for i in xrange(num_consumers) ]  
  75.     for w in consumers:  
  76.         w.start()  
  77.   
  78.     # Enqueue jobs  
  79.     num_jobs = 10  
  80.     for i in xrange(num_jobs):  
  81.         tasks.put(Task(i, i))  
  82.   
  83.     # Add a poison pill for each consumer  
  84.     for i in xrange(num_consumers):  
  85.         tasks.put(None)  
  86.   
  87.     # Wait for all of the tasks to finish  
  88.     tasks.join()  
  89.   
  90.     # Start printing results  
  91.     while num_jobs:  
  92.         result = results.get()  
  93.         print 'Result:', result  
  94.         num_jobs -= 1  

进程间信号传递

Event提供一种简单的方法,可以在进程间传递状态信息。事件可以切换设置和未设置状态。通过使用一个可选的超时值,时间对象的用户可以等待其状态从未设置变为设置。

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3.   
  4. def wait_for_event(e):  
  5.     """Wait for the event to be set before doing anything"""  
  6.     print 'wait_for_event: starting'  
  7.     e.wait()  
  8.     print 'wait_for_event: e.is_set()->', e.is_set()  
  9.   
  10. def wait_for_event_timeout(e, t):  
  11.     """Wait t seconds and then timeout"""  
  12.     print 'wait_for_event_timeout: starting'  
  13.     e.wait(t)  
  14.     print 'wait_for_event_timeout: e.is_set()->', e.is_set()  
  15.   
  16. if __name__ == '__main__':  
  17.     e = multiprocessing.Event()  
  18.     w1 = multiprocessing.Process(name='block',   
  19.                                  target=wait_for_event,  
  20.                                  args=(e,))  
  21.     w1.start()  
  22.   
  23.     w2 = multiprocessing.Process(name='nonblock',   
  24.                                  target=wait_for_event_timeout,   
  25.                                  args=(e, 2))  
  26.     w2.start()  
  27.   
  28.     print 'main: waiting before calling Event.set()'  
  29.     time.sleep(3)  
  30.     e.set()  
  31.     print 'main: event is set'  

由于Python设计的限制(我说的是咱们常用的CPython)。最多只能用满1个CPU核心。
Python提供了非常好用的多进程包multiprocessing,你只需要定义一个函数,Python会替你完成其他所有事情。借助这个包,可以轻松完成从单进程到并发执行的转换。

1、新建单一进程

如果我们新建少量进程,可以如下:

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3. def func(msg):  
  4.   for i in xrange(3):  
  5.     print msg  
  6.     time.sleep(1)  
  7. if __name__ == "__main__":  
  8.   p = multiprocessing.Process(target=func, args=("hello", ))  
  9.   p.start()  
  10.   p.join()  
  11.   print "Sub-process done."  

2、使用进程池(非阻塞)

是的,你没有看错,不是线程池。它可以让你跑满多核CPU,而且使用方法非常简单。

注意要用apply_async,如果落下async,就变成阻塞版本了。

processes=4是最多并发进程数量。

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3. def func(msg):  
  4.   for i in xrange(3):  
  5.     print msg  
  6.     time.sleep(1)  
  7. if __name__ == "__main__":  
  8.   pool = multiprocessing.Pool(processes=4)  
  9.   for i in xrange(10):  
  10.     msg = "hello %d" %(i)  
  11.     pool.apply_async(func, (msg, ))  
  12.   pool.close()  
  13.   pool.join()  
  14.   print "Sub-process(es) done."  

函数解释

  • apply_async(func[, args[, kwds[, callback]]]) 它是非阻塞,apply(func[, args[, kwds]])是阻塞
  • close()    关闭pool,使其不在接受新的任务。
  • terminate()    结束工作进程,不在处理未完成的任务。
  • join()    主进程阻塞,等待子进程的退出, join方法要在close或terminate之后使用。

使用进程池(阻塞)

[python]  view plain  copy
  1. #coding: utf-8  
  2. import multiprocessing  
  3. import time  
  4.   
  5. def func(msg):  
  6.     print "msg:", msg  
  7.     time.sleep(3)  
  8.     print "end"  
  9.   
  10. if __name__ == "__main__":  
  11.     pool = multiprocessing.Pool(processes = 3)  
  12.     for i in xrange(4):  
  13.         msg = "hello %d" %(i)  
  14.         pool.apply(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去  
  15.   
  16.     print "Mark~ Mark~ Mark~~~~~~~~~~~~~~~~~~~~~~"  
  17.     pool.close()  
  18.     pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束  
  19.     print "Sub-process(es) done."  


3、使用Pool,并需要关注结果

更多的时候,我们不仅需要多进程执行,还需要关注每个进程的执行结果,如下:

[python]  view plain  copy
  1. import multiprocessing  
  2. import time  
  3. def func(msg):  
  4.   for i in xrange(3):  
  5.     print msg  
  6.     time.sleep(1)  
  7.   return "done " + msg  
  8. if __name__ == "__main__":  
  9.   pool = multiprocessing.Pool(processes=4)  
  10.   result = []  
  11.   for i in xrange(10):  
  12.     msg = "hello %d" %(i)  
  13.     result.append(pool.apply_async(func, (msg, )))  
  14.   pool.close()  
  15.   pool.join()  
  16.   for res in result:  
  17.     print res.get()  
  18.   print "Sub-process(es) done."  

示例 

[python]  view plain  copy
  1. import multiprocessing  
  2.   
  3. def do_calculation(data):  
  4.     return data*2  
  5.   
  6. def start_process():  
  7.     print 'Starting', multiprocessing.current_process().name  
  8.   
  9. if __name__ == '__main__':  
  10.     inputs = list(range(10))  
  11.     print 'Inputs  :', inputs  
  12.   
  13.     builtin_output = map(do_calculation, inputs)  
  14.     print 'Build-In :', builtin_output  
  15.   
  16.     pool_size = multiprocessing.cpu_count()*2  
  17.     pool = multiprocessing.Pool(processes=pool_size, initializer=start_process,)  
  18.     # 默认情况下,Pool会创建固定数目的工作进程,并向这些工作进程传递作业,直到再没有更多作业为止。  
  19.     # maxtasksperchild参数为每个进程执行task的最大数目,  
  20.     # 设置maxtasksperchild参数可以告诉池在完成一定数量任务之后重新启动一个工作进程,  
  21.     # 来避免运行时间很长的工作进程消耗太多的系统资源。  
  22.     # pool = multiprocessing.Pool(processes=pool_size, initializer=start_process, maxtasksperchild=2)  
  23.     print '-' * 20  
  24.     pool_outputs = pool.map(do_calculation, inputs)  
  25.     pool.close()  
  26.     pool.join()  
  27.   
  28.     print 'Pool  :', pool_outputs  

使用多个进程池

[python]  view plain  copy
  1. #coding: utf-8  
  2. import multiprocessing  
  3. import os, time, random  
  4.   
  5. def Lee():  
  6.     print "\nRun task Lee-%s" %(os.getpid()) #os.getpid()获取当前的进程的ID  
  7.     start = time.time()  
  8.     time.sleep(random.random() * 10#random.random()随机生成0-1之间的小数  
  9.     end = time.time()  
  10.     print 'Task Lee, runs %0.2f seconds.' %(end - start)  
  11.   
  12. def Marlon():  
  13.     print "\nRun task Marlon-%s" %(os.getpid())  
  14.     start = time.time()  
  15.     time.sleep(random.random() * 40)  
  16.     end=time.time()  
  17.     print 'Task Marlon runs %0.2f seconds.' %(end - start)  
  18.   
  19. def Allen():  
  20.     print "\nRun task Allen-%s" %(os.getpid())  
  21.     start = time.time()  
  22.     time.sleep(random.random() * 30)  
  23.     end = time.time()  
  24.     print 'Task Allen runs %0.2f seconds.' %(end - start)  
  25.   
  26. def Frank():  
  27.     print "\nRun task Frank-%s" %(os.getpid())  
  28.     start = time.time()  
  29.     time.sleep(random.random() * 20)  
  30.     end = time.time()  
  31.     print 'Task Frank runs %0.2f seconds.' %(end - start)  
  32.           
  33. if __name__=='__main__':  
  34.     function_list=  [Lee, Marlon, Allen, Frank]   
  35.     print "parent process %s" %(os.getpid())  
  36.   
  37.     pool=multiprocessing.Pool(4)  
  38.     for func in function_list:  
  39.         pool.apply_async(func)     #Pool执行函数,apply执行函数,当有一个进程执行完毕后,会添加一个新的进程到pool中  
  40.   
  41.     print 'Waiting for all subprocesses done...'  
  42.     pool.close()  
  43.     pool.join()    #调用join之前,一定要先调用close() 函数,否则会出错, close()执行后不会有新的进程加入到pool,join函数等待素有子进程结束  
  44.     print 'All subprocesses done.'  
multiprocessing pool map
[python]  view plain  copy
  1. #coding: utf-8  
  2. import multiprocessing   
  3.   
  4. def m1(x):   
  5.     print x * x   
  6.   
  7. if __name__ == '__main__':   
  8.     pool = multiprocessing.Pool(multiprocessing.cpu_count())   
  9.     i_list = range(8)  
  10.     pool.map(m1, i_list)  

[python]  view plain  copy
  1. #coding: utf-8  
  2. import multiprocessing  
  3. import logging  
  4.   
  5. def create_logger(i):  
  6.     print i  
  7.   
  8. class CreateLogger(object):  
  9.     def __init__(self, func):  
  10.         self.func = func  
  11.   
  12. if __name__ == '__main__':  
  13.     ilist = range(10)  
  14.   
  15.     cl = CreateLogger(create_logger)  
  16.     pool = multiprocessing.Pool(multiprocessing.cpu_count())  
  17.     pool.map(cl.func, ilist)  
  18.   
  19.     print "hello------------>"  


Python 多进程 multiprocessing.Pool类详解

multiprocessing模块


multiprocessing包是Python中的多进程管理包。它与 threading.Thread类似,可以利用multiprocessing.Process对象来创建一个进程。该进程可以允许放在Python程序内部编写的函数中。该Process对象与Thread对象的用法相同,拥有is_alive()、join([timeout])、run()、start()、terminate()等方法。属性有:authkey、daemon(要通过start()设置)、exitcode(进程在运行时为None、如果为–N,表示被信号N结束)、name、pid。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类,用来同步进程,其用法也与threading包中的同名类一样。multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

这个模块表示像线程一样管理进程,这个是multiprocessing的核心,它与threading很相似,对多核CPU的利用率会比threading好的多。

看一下Process类的构造方法:

[plain]  view plain  copy
  1. __init__(self, group=None, target=None, name=None, args=(), kwargs={})  

参数说明: 
group
:进程所属组。基本不用 
target:表示调用对象。 
args:表示调用对象的位置参数元组。 
name:别名 
kwargs:表示调用对象的字典。

创建进程的简单实例:

[python]  view plain  copy
  1. #coding=utf-8  
  2. import multiprocessing  
  3.   
  4. def do(n) :  
  5.   #获取当前线程的名字  
  6.   name = multiprocessing.current_process().name  
  7.   print name,'starting'  
  8.   print "worker ", n  
  9.   return   
  10.   
  11. if __name__ == '__main__' :  
  12.   numList = []  
  13.   for i in xrange(5) :  
  14.     p = multiprocessing.Process(target=do, args=(i,))  
  15.     numList.append(p)  
  16.     p.start()  
  17.     p.join()  
  18.     print "Process end."  
执行结果:
[python]  view plain  copy
  1. Process-1 starting  
  2. worker  0  
  3. Process end.  
  4. Process-2 starting  
  5. worker  1  
  6. Process end.  
  7. Process-3 starting  
  8. worker  2  
  9. Process end.  
  10. Process-4 starting  
  11. worker  3  
  12. Process end.  
  13. Process-5 starting  
  14. worker  4  
  15. Process end.  

创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,并用其start()方法启动,这样创建进程比fork()还要简单。 
join()方法表示等待子进程结束以后再继续往下运行,通常用于进程间的同步。

注意: 
在Windows上要想使用进程模块,就必须把有关进程的代码写在当前.py文件的if __name__ == ‘__main__’ :语句的下面,才能正常使用Windows下的进程模块。Unix/Linux下则不需要。

   Pool类

在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作的对象数目不大时,还可以直接使用Process类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时进程池就派上用场了。
Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。


Pool类描述了一个工作进程池,他有几种不同的方法让任务卸载工作进程。 进程池内部维护一个进程序列,当使用时,则去进程池中获取一个进程,如果进程池序列中没有可供使用的进进程,那么程序就会等待,直到进程池中有可用进程为止。我们可以用Pool类创建一个进程池,展开提交的任务给进程池。


一个进程池对象可以控制工作进程池的哪些工作可以被提交,它支持超时和回调的异步结果,有一个类似map的实现。
processes :            使用的工作进程的数量,如果processes是None那么使用os.cpu_count()返回的数量。
initializer:               如果initializer是None,那么每一个工作进程在开始的时候会调用initializer(*initargs)。 
maxtasksperchild:工作进程退出之前可以完成的任务数,完成后用一个心的工作进程来替代原进程,来让闲置的资源被释放。

                                   maxtasksperchild默认是None,意味着只要Pool存在工作进程就会一直存活。 
context:                     用在制定工作进程启动时的上下文,一般使用multiprocessing.Pool() 或者一个context对象的Pool()方法来创建一个池,两种方法都适当的设置了context

注意:Pool对象的方法只可以被创建pool的进程所调用。

下面介绍一下multiprocessing 模块下的Pool类下的几个方法

进程池的方法 

[plain]  view plain  copy
  1. apply(func[, args[, kwds]]) :使用arg和kwds参数调用func函数,结果返回前会一直阻塞,  
  2.                               由于这个原因,apply_async()更适合并发执行,另外,func函数仅被pool中的一个进程运行。     
  3.   
  4. apply_async(func[, args[, kwds[, callback[, error_callback]]]]) :   
  5.         apply()方法的一个变体,会返回一个结果对象。  
  6.         如果callback被指定,那么callback可以接收一个参数然后被调用,当结果准备好回调时会调用callback,  
  7.     调用失败时,则用error_callback替换callback。 Callbacks应被立即完成,否则处理结果的线程会被阻塞。     
  8. close() :    阻止更多的任务提交到pool,待任务完成后,工作进程会退出。     
  9. terminate() :不管任务是否完成,立即停止工作进程。在对pool对象进程垃圾回收的时候,会立即调用terminate()。  
  10. join() :      wait工作线程的退出,在调用join()前,必须调用close() or terminate()。  
  11.               这样是因为被终止的进程需要被父进程调用wait(join等价与wait),否则进程会成为僵尸进程。     
  12. map(func, iterable[, chunksize])     
  13. map_async(func, iterable[, chunksize[, callback[, error_callback]]])  
  14. imap(func, iterable[, chunksize])  
  15. imap_unordered(func, iterable[, chunksize])     
  16. starmap(func, iterable[, chunksize])  
  17. starmap_async(func, iterable[, chunksize[, callback[, error_back]]])  

   apply()

函数原型:

[plain]  view plain  copy
  1. apply(func[, args=()[, kwds={}]])  

该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。

apply 方法 示例

[python]  view plain  copy
  1. # apply  
  2. from multiprocessing import Pool  
  3. import time  
  4.   
  5. def f1(arg):  
  6.     time.sleep(0.5)  
  7.     print(arg)  
  8.     return arg + 100  
  9.   
  10. if __name__ == "__main__":  
  11.     pool = Pool(5)  
  12.     for i in range(110):  
  13.         pool.apply(func=f1, args=(i,))  

   apply_async()

函数原型:

[plain]  view plain  copy
  1. apply_async(func[, args=()[, kwds={}[, callback=None]]])  

与apply用法一样,但它是非阻塞且支持结果返回进行回调。

apply_async 方法 示例

[python]  view plain  copy
  1. # apply_async  
  2. from multiprocessing import Pool  
  3.   
  4. def f1(i):  
  5.     time.sleep(1)  
  6.     print(i)  
  7.     return i + 100  
  8.   
  9. def f2(arg):  
  10.     print(arg)  
  11.   
  12. if __name__ == "__main__":  
  13.     pool = Pool(5)  
  14.     for i in range(110):  
  15.         pool.apply_async(func=f1, args=(i,), callback=f2)  
  16.     pool.close()  
  17.     pool.join()  

   map()

函数原型:

[plain]  view plain  copy
  1. map(func, iterable[, chunksize=None])  

Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。 
注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。

   close()

关闭进程池(pool),使其不在接受新的任务。

   terminate()

结束工作进程,不在处理未处理的任务。

   join()

主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。

multiprocessing.Pool类的实例:

[plain]  view plain  copy
  1. import time  
  2. from multiprocessing import Pool  
  3. def run(fn):  
  4.   #fn: 函数参数是数据列表的一个元素  
  5.   time.sleep(1)  
  6.   return fn*fn  
  7.   
  8. if __name__ == "__main__":  
  9.   testFL = [1,2,3,4,5,6]    
  10.   print 'shunxu:' #顺序执行(也就是串行执行,单进程)  
  11.   s = time.time()  
  12.   for fn in testFL:  
  13.     run(fn)  
  14.   
  15.   e1 = time.time()  
  16.   print "顺序执行时间:", int(e1 - s)  
  17.   
  18.   print 'concurrent:' #创建多个进程,并行执行  
  19.   pool = Pool(5)  #创建拥有5个进程数量的进程池  
  20.   #testFL:要处理的数据列表,run:处理testFL列表中数据的函数  
  21.   rl =pool.map(run, testFL)   
  22.   pool.close()#关闭进程池,不再接受新的进程  
  23.   pool.join()#主进程阻塞等待子进程的退出  
  24.   e2 = time.time()  
  25.   print "并行执行时间:", int(e2-e1)  
  26.   print rl  
执行结果:
[plain]  view plain  copy
  1. shunxu:  
  2. 顺序执行时间: 6  
  3. concurrent:  
  4. 并行执行时间: 2  
  5. [1, 4, 9, 16, 25, 36]  

上例是一个创建多个进程并发处理与顺序执行处理同一数据,所用时间的差别。从结果可以看出,并发执行的时间明显比顺序执行要快很多,但是进程是要耗资源的,所以平时工作中,进程数也不能开太大。
程序中的r1表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,这个结果存在一个列表中,也就是一个结果堆中,实际上是用了队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),让其不再接受新的Process了。

再看一个实例:

[plain]  view plain  copy
  1. import time  
  2. from multiprocessing import Pool  
  3. def run(fn) :  
  4.   time.sleep(2)  
  5.   print fn  
  6. if __name__ == "__main__" :  
  7.   startTime = time.time()  
  8.   testFL = [1,2,3,4,5]  
  9.   pool = Pool(10)#可以同时跑10个进程  
  10.   pool.map(run,testFL)  
  11.   pool.close()  
  12.   pool.join()     
  13.   endTime = time.time()  
  14.   print "time :", endTime - startTime  
执行结果:
[plain]  view plain  copy
  1. 21  
  2.   
  3. 3  
  4. 4  
  5. 5  
  6. time : 2.51999998093  
再次执行结果如下:
[plain]  view plain  copy
  1. 1  
  2. 34  
  3.   
  4. 2  
  5. 5  
  6. time : 2.48600006104  

结果中为什么还有空行和没有折行的数据呢?其实这跟进程调度有关,当有多个进程并行执行时,每个进程得到的时间片时间不一样,哪个进程接受哪个请求以及执行完成时间都是不定的,所以会出现输出乱序的情况。那为什么又会有没这行和空行的情况呢?因为有可能在执行第一个进程时,刚要打印换行符时,切换到另一个进程,这样就极有可能两个数字打印到同一行,并且再次切换回第一个进程时会打印一个换行符,所以就会出现空行的情况。

   进程实战实例

并行处理某个目录下文件中的字符个数和行数,存入res.txt文件中, 
每个文件一行,格式为:filename:lineNumber,charNumber

[python]  view plain  copy
  1. import os  
  2. import time  
  3. from multiprocessing import Pool  
  4.   
  5. def getFile(path) :  
  6.   #获取目录下的文件list  
  7.   fileList = []  
  8.   for root, dirs, files in list(os.walk(path)) :  
  9.     for i in files :  
  10.       if i.endswith('.txt'or i.endswith('.10w') :  
  11.         fileList.append(root + "\\" + i)  
  12.   return fileList  
  13.   
  14. def operFile(filePath) :  
  15.   #统计每个文件中行数和字符数,并返回  
  16.   filePath = filePath  
  17.   fp = open(filePath)  
  18.   content = fp.readlines()  
  19.   fp.close()  
  20.   lines = len(content)  
  21.   alphaNum = 0  
  22.   for i in content :  
  23.     alphaNum += len(i.strip('\n'))  
  24.   return lines,alphaNum,filePath  
  25.   
  26. def out(list1, writeFilePath) :  
  27.   #将统计结果写入结果文件中  
  28.   fileLines = 0  
  29.   charNum = 0  
  30.   fp = open(writeFilePath,'a')  
  31.   for i in list1 :  
  32.     fp.write(i[2] + " 行数:"+ str(i[0]) + " 字符数:"+str(i[1]) + "\n")  
  33.     fileLines += i[0]  
  34.     charNum += i[1]  
  35.   fp.close()  
  36.   print fileLines, charNum  
  37.   
  38. if __name__ == "__main__":  
  39.   #创建多个进程去统计目录中所有文件的行数和字符数  
  40.   startTime = time.time()  
  41.   filePath = "C:\\wcx\\a"  
  42.   fileList = getFile(filePath)  
  43.   pool = Pool(5)    
  44.   resultList =pool.map(operFile, fileList)    
  45.   pool.close()  
  46.   pool.join()  
  47.   
  48.   writeFilePath = "c:\\wcx\\res.txt"  
  49.   print resultList  
  50.   out(resultList, writeFilePath)  
  51.   endTime = time.time()  
  52.   print "used time is ", endTime - startTime  

执行结果:

1
耗时不到1秒,可见多进程并发执行速度是很快的。


我们已经见过了使用subprocess包来创建子进程,但这个包有两个很大的局限性:

1) 我们总是让subprocess运行外部的程序,而不是运行一个Python脚本内部编写的函数。

2) 进程间只通过管道进行文本交流。以上限制了我们将subprocess包应用到更广泛的多进程任务。

   (这样的比较实际是不公平的,因为subprocessing本身就是设计成为一个shell,而不是一个多进程管理包)

threading和multiprocessing

(请尽量先阅读Python多线程与同步)

multiprocessing包是Python中的多进程管理包。与threading.Thread类似,它可以利用multiprocessing.Process对象来创建一个进程。该进程可以运行在Python程序内部编写的函数。该Process对象与Thread对象的用法相同,也有start(), run(), join()的方法。此外multiprocessing包中也有Lock/Event/Semaphore/Condition类 (这些对象可以像多线程那样,通过参数传递给各个进程),用以同步进程,其用法与threading包中的同名类一致。所以,multiprocessing的很大一部份与threading使用同一套API,只不过换到了多进程的情境。

但在使用这些共享API的时候,我们要注意以下几点:

  • 在UNIX平台上,当某个进程终结之后,该进程需要被其父进程调用wait,否则进程成为僵尸进程(Zombie)。所以,有必要对每个Process对象调用join()方法 (实际上等同于wait)。对于多线程来说,由于只有一个进程,所以不存在此必要性。
  • multiprocessing提供了threading包中没有的IPC(比如Pipe和Queue),效率上更高。应优先考虑Pipe和Queue,避免使用Lock/Event/Semaphore/Condition等同步方式 (因为它们占据的不是用户进程的资源)。
  • 多进程应该避免共享资源。在多线程中,我们可以比较容易地共享资源,比如使用全局变量或者传递参数。在多进程情况下,由于每个进程有自己独立的内存空间,以上方法并不合适。此时我们可以通过共享内存和Manager的方法来共享资源。但这样做提高了程序的复杂度,并因为同步的需要而降低了程序的效率。

Process.PID中保存有PID,如果进程还没有start(),则PID为None。

我们可以从下面的程序中看到Thread对象和Process对象在使用上的相似性与结果上的不同。各个线程和进程都做一件事:打印PID。但问题是,所有的任务在打印的时候都会向同一个标准输出(stdout)输出。这样输出的字符会混合在一起,无法阅读。使用Lock同步,在一个任务输出完成之后,再允许另一个任务输出,可以避免多个任务同时向终端输出。

[python]  view plain  copy
  1. # Similarity and difference of multi thread vs. multi process  
  2. # Written by Vamei  
  3.   
  4. import os  
  5. import threading  
  6. import multiprocessing  
  7.   
  8. # worker function  
  9. def worker(sign, lock):  
  10.     lock.acquire()  
  11.     print(sign, os.getpid())  
  12.     lock.release()  
  13.   
  14. # Main  
  15. print('Main:',os.getpid())  
  16.   
  17. # Multi-thread  
  18. record = []  
  19. lock  = threading.Lock()  
  20. for i in range(5):  
  21.     thread = threading.Thread(target=worker,args=('thread',lock))  
  22.     thread.start()  
  23.     record.append(thread)  
  24.   
  25. for thread in record:  
  26.     thread.join()  
  27.   
  28. # Multi-process  
  29. record = []  
  30. lock = multiprocessing.Lock()  
  31. for i in range(5):  
  32.     process = multiprocessing.Process(target=worker,args=('process',lock))  
  33.     process.start()  
  34.     record.append(process)  
  35.   
  36. for process in record:  
  37.     process.join()  

所有Thread的PID都与主程序相同,而每个Process都有一个不同的PID。

(练习: 使用mutiprocessing包将Python多线程与同步中的多线程程序更改为多进程程序)

  Pipe和Queue

正如我们在Linux多线程中介绍的管道PIPE和消息队列message queue,multiprocessing包中有Pipe和Queue类来分别支持这两种IPC机制。Pipe和Queue可以用来传送常见的对象。

1) Pipe可以是单向(half-duplex),也可以是双向(duplex)。我们通过mutiprocessing.Pipe(duplex=False)创建单向管道 (默认为双向)。一个进程从PIPE一端输入对象,然后被PIPE另一端的进程接收,单向管道只允许管道一端的进程输入,而双向管道则允许从两端输入。

下面的程序展示了Pipe的使用:

[python]  view plain  copy
  1. # Multiprocessing with Pipe  
  2. # Written by Vamei  
  3.   
  4. import multiprocessing as mul  
  5.   
  6. def proc1(pipe):  
  7.     pipe.send('hello')  
  8.     print('proc1 rec:',pipe.recv())  
  9.   
  10. def proc2(pipe):  
  11.     print('proc2 rec:',pipe.recv())  
  12.     pipe.send('hello, too')  
  13.   
  14. # Build a pipe  
  15. pipe = mul.Pipe()  
  16.   
  17. # Pass an end of the pipe to process 1  
  18. p1   = mul.Process(target=proc1, args=(pipe[0],))  
  19. # Pass the other end of the pipe to process 2  
  20. p2   = mul.Process(target=proc2, args=(pipe[1],))  
  21. p1.start()  
  22. p2.start()  
  23. p1.join()  
  24. p2.join()  

这里的Pipe是双向的。

Pipe对象建立的时候,返回一个含有两个元素的表,每个元素代表Pipe的一端(Connection对象)。我们对Pipe的某一端调用send()方法来传送对象,在另一端使用recv()来接收。

2) Queue与Pipe相类似,都是先进先出的结构。但Queue允许多个进程放入,多个进程从队列取出对象。Queue使用mutiprocessing.Queue(maxsize)创建,maxsize表示队列中可以存放对象的最大数量。

下面的程序展示了Queue的使用:

[python]  view plain  copy
  1. # Written by Vamei  
  2. import os  
  3. import multiprocessing  
  4. import time  
  5. #==================  
  6. # input worker  
  7. def inputQ(queue):  
  8.     info = str(os.getpid()) + '(put):' + str(time.time())  
  9.     queue.put(info)  
  10.   
  11. # output worker  
  12. def outputQ(queue,lock):  
  13.     info = queue.get()  
  14.     lock.acquire()  
  15.     print (str(os.getpid()) + '(get):' + info)  
  16.     lock.release()  
  17. #===================  
  18. # Main  
  19. record1 = []   # store input processes  
  20. record2 = []   # store output processes  
  21. lock  = multiprocessing.Lock()    # To prevent messy print  
  22. queue = multiprocessing.Queue(3)  
  23.   
  24. # input processes  
  25. for i in range(10):  
  26.     process = multiprocessing.Process(target=inputQ,args=(queue,))  
  27.     process.start()  
  28.     record1.append(process)  
  29.   
  30. # output processes  
  31. for i in range(10):  
  32.     process = multiprocessing.Process(target=outputQ,args=(queue,lock))  
  33.     process.start()  
  34.     record2.append(process)  
  35.   
  36. for p in record1:  
  37.     p.join()  
  38.   
  39. queue.close()  # No more object will come, close the queue  
  40.   
  41. for p in record2:  
  42.     p.join()  
一些进程使用put()在Queue中放入字符串,这个字符串中包含PID和时间。另一些进程从Queue中取出,并打印自己的PID以及get()的字符串

   进程池

进程池 (Process Pool)可以创建多个进程。这些进程就像是随时待命的士兵,准备执行任务(程序)。一个进程池中可以容纳多个待命的士兵。

比如下面的程序:
[python]  view plain  copy
  1. import multiprocessing as mul  
  2.   
  3. def f(x):  
  4.     return x**2  
  5.   
  6. pool = mul.Pool(5)  
  7. rel  = pool.map(f,[1,2,3,4,5,6,7,8,9,10])  
  8. print(rel)  

我们创建了一个容许5个进程的进程池 (Process Pool) 。Pool运行的每个进程都执行f()函数。我们利用map()方法,将f()函数作用到表的每个元素上。这与built-in的map()函数类似,只是这里用5个进程并行处理。如果进程运行结束后,还有需要处理的元素,那么的进程会被用于重新运行f()函数。除了map()方法外,Pool还有下面的常用方法。

apply_async(func,args)  从进程池中取出一个进程执行func,args为func的参数。它将返回一个AsyncResult的对象,你可以对该对象调用get()方法以获得结果。

close()  进程池不再创建新的进程

join()   wait进程池中的全部进程。必须对Pool先调用close()方法才能join。

练习

有下面一个文件download.txt。

www.sina.com.cn
www.163.com
www.iciba.com
www.cnblogs.com
www.qq.com
www.douban.com

使用包含3个进程的进程池下载文件中网站的首页。(你可以使用subprocess调用wget或者curl等下载工具执行具体的下载任务)

共享资源

我们在Python多进程初步已经提到,我们应该尽量避免多进程共享资源。多进程共享资源必然会带来进程间相互竞争。而这种竞争又会造成race condition,我们的结果有可能被竞争的不确定性所影响。但如果需要,我们依然可以通过共享内存和Manager对象这么做。

共享内存

Linux进程间通信中,我们已经讲述了共享内存(shared memory)的原理,这里给出用Python实现的例子:

[python]  view plain  copy
  1. # modified from official documentation  
  2. import multiprocessing  
  3.   
  4. def f(n, a):  
  5.     n.value   = 3.14  
  6.     a[0]      = 5  
  7.   
  8. num   = multiprocessing.Value('d'0.0)  
  9. arr   = multiprocessing.Array('i', range(10))  
  10.   
  11. p = multiprocessing.Process(target=f, args=(num, arr))  
  12. p.start()  
  13. p.join()  
  14.   
  15. print num.value  
  16. print arr[:]  

这里我们实际上只有主进程和Process对象代表的进程。我们在主进程的内存空间中创建共享的内存,也就是Value和Array两个对象。对象Value被设置成为双精度数(d), 并初始化为0.0。而Array则类似于C中的数组,有固定的类型(i, 也就是整数)。在Process进程中,我们修改了Value和Array对象。回到主程序,打印出结果,主程序也看到了两个对象的改变,说明资源确实在两个进程之间共享。

 

Manager

Manager对象类似于服务器与客户之间的通信 (server-client),与我们在Internet上的活动很类似。我们用一个进程作为服务器,建立Manager来真正存放资源。其它的进程可以通过参数传递或者根据地址来访问Manager,建立连接后,操作服务器上的资源。在防火墙允许的情况下,我们完全可以将Manager运用于多计算机,从而模仿了一个真实的网络情境。下面的例子中,我们对Manager的使用类似于shared memory,但可以共享更丰富的对象类型。
[python]  view plain  copy
  1. import multiprocessing  
  2.   
  3. def f(x, arr, l):  
  4.     x.value = 3.14  
  5.     arr[0] = 5  
  6.     l.append('Hello')  
  7.   
  8. server = multiprocessing.Manager()  
  9. x    = server.Value('d'0.0)  
  10. arr  = server.Array('i', range(10))  
  11. l    = server.list()  
  12.   
  13. proc = multiprocessing.Process(target=f, args=(x, arr, l))  
  14. proc.start()  
  15. proc.join()  
  16.   
  17. print(x.value)  
  18. print(arr)  
  19. print(l)  

Manager利用list()方法提供了表的共享方式。实际上你可以利用dict()来共享词典,Lock()来共享threading.Lock(注意,我们共享的是threading.Lock,而不是进程的mutiprocessing.Lock。后者本身已经实现了进程共享)等。 这样Manager就允许我们共享更多样的对象。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值