开线程、开进程
concurrent.futures模块的基础是Exectuor
,Executor是一个抽象类,它不能被直接使用。但是它提供的两个子类ThreadPoolExecutor
和ProcessPoolExecutor
却是非常有用,顾名思义两者分别被用来创建线程池和进程池的代码。
如果你依然在坚守Python2.x,请先安装futures模块。
pip install futures
import time from concurrent.futures import ThreadPoolExecutor pool = ThreadPoolExecutor(10) #ProcessPoolExecutor() def test1(num): print("name", num) time.sleep(10) def test2(): for item in range(20): pool.submit(test1, item) pool.shutdown(wait=True) # 等待所有线程执行完毕 if __name__ == '__main__': test2()
pool.shutdown(wait=True) 提交/调用一个任务,同步
pool.shutdown(wait=False) 提交/调用一个任务,异步非阻塞
注意:
在Windows上要想使用进程模块,就必须把有关进程的代码写在当前.py文件的if __name__ == ‘__main__’ :语句的下面,才能正常使用Windows下的进程模块。Unix/Linux下则不需要。
Attempt to start a new process before the current process has finished its bootstrapping phase. This probably means that you are on Windows and you have forgotten to use the proper idiom in the main module: if __name__ == '__main__': freeze_support() ... The "freeze_support()" line can be omitted if the program is not going to be frozen to produce a Windows executable.
使用submit来操作线程池/进程池
from concurrent.futures import ThreadPoolExecutor import time def return_future_result(message): time.sleep(2) return message pool = ThreadPoolExecutor(max_workers=2) # 创建一个最大可容纳2个task的线程池 future1 = pool.submit(return_future_result, ("hello")) # 往线程池里面加入一个task future2 = pool.submit(return_future_result, ("world")) # 往线程池里面加入一个task print(future1.done()) # 判断task1是否结束 time.sleep(3) print(future2.done()) # 判断task2是否结束 print(future1.result()) # 查看task1返回的结果 print(future2.result()) # 查看task2返回的结果
我们根据运行结果来分析一下。我们使用submit方法来往线程池中加入一个task,submit返回一个Future对象,对于Future对象可以简单地理解为一个在未来完成的操作。在第一个print语句中很明显因为time.sleep(2)的原因我们的future1没有完成,因为我们使用time.sleep(3)暂停了主线程,所以到第二个print语句的时候我们线程池里的任务都已经全部结束。
False True hello world # 在上述程序执行的过程中,通过ps命令我们可以看到三个线程同时在后台运行 ziwenxie :: ~ » ps -eLf | grep python ziwenxie 8361 7557 8361 3 3 19:45 pts/0 00:00:00 python example1.py ziwenxie 8361 7557 8362 0 3 19:45 pts/0 00:00:00 python example1.py ziwenxie 8361 7557 8363 0 3 19:45 pts/0 00:00:00 python example1.py
使用map来操作线程池/进程池(确保返回的结果有序)
import concurrent.futures import requests URLS = ['http://www.baidu.com', 'http://www.weibo.com','https://www.cnblogs.com'] def load_url(url): print url conn=requests.get(url, timeout=60) return conn.content # 可以使用with语句来确保线程被及时清理 with concurrent.futures.ThreadPoolExecutor(max_workers=3) as pool: for url, data in zip(URLS, pool.map(load_url, URLS)): print('%s page is %s bytes' % (url, len(data)))
从运行结果可以看出,map是按照URLS列表元素的顺序返回的,并且写出的代码更加简洁直观,我们可以根据具体的需求任选一种。
http://www.baidu.com page is 2381 bytes http://www.weibo.com page is 6117 bytes https://www.cnblogs.com page is 45969 bytes
使用wait来操作线程池/进程池
wait方法接会返回一个tuple(元组),tuple中包含两个set(集合),一个是completed(已完成的),另外一个是uncompleted(未完成的)。使用wait方法的一个优势就是获得更大的自由度,它接收三个参数FIRST_COMPLETED, FIRST_EXCEPTION 和ALL_COMPLETE,默认设置为ALL_COMPLETED。
from concurrent.futures import ThreadPoolExecutor, wait, as_completed from time import sleep from random import randint def return_after_random_secs(num): sleep(randint(1, 5)) return "Return of {}".format(num) pool = ThreadPoolExecutor(5) futures = [] for i in range(5): futures.append(pool.submit(return_after_random_secs, i)) # future=pool.submit(return_after_random_secs, i) future对象 print(wait(futures)) # print(wait(futures, timeout=None, return_when='FIRST_COMPLETED'))
如果采用默认的ALL_COMPLETED,程序会阻塞直到线程池里面的所有任务都完成。
ziwenxie :: ~ » python example5.py DoneAndNotDoneFutures(done={ <Future at 0x7f0b06c9bc88 state=finished returned str>, <Future at 0x7f0b06cbaa90 state=finished returned str>, <Future at 0x7f0b06373898 state=finished returned str>, <Future at 0x7f0b06352ba8 state=finished returned str>, <Future at 0x7f0b06373b00 state=finished returned str>}, not_done=set())
如果采用FIRST_COMPLETED参数,程序并不会等到线程池里面所有的任务都完成。
DoneAndNotDoneFutures(done={ <Future at 0x7f84109edb00 state=finished returned str>, <Future at 0x7f840e2e9320 state=finished returned str>, <Future at 0x7f840f25ccc0 state=finished returned str>}, not_done={<Future at 0x7f840e2e9ba8 state=running>, <Future at 0x7f840e2e9940 state=running>})
多进程报错
PicklingError: Can't pickle <type 'thread.lock'>: attribute lookup thread.lock failed
错误原因:使用队列错误(Queue.Queue()不能用于进程间通信) 当子进程执行的方法定义在类中时会报错:PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup __builtin__.instancemethod failed
Pickle.PicklingError: Can't pickle <type 'instancemethod'>: attribute lookup __builtin__.instancemethod failed
pool
方法都使用了queue.Queue将task传递给工作进程。multiprocessing
必须将数据序列化以在进程间传递。方法只有在模块的顶层时才能被序列化,跟类绑定的方法不能被序列化,就会出现上面的异常。
解决方法:
- 用线程替换进程
- 可以使用copy_reg来规避上面的异常.
- 将类方法独立出来
- 使用pathos.multiprocessing,而不是multiprocessing。 pathos.multiprocessing是使用dill的多进程的一个分支。dill可以序列化几乎任何东西在Python中,所以你可以发送更多的东西并行

import multiprocessing class MyTask(object): def task(self, x): return x*x def run(self): pool = multiprocessing.Pool(processes=3) a = [1, 2, 3] ret=pool.map(self.task, a) print ret if __name__ == '__main__': t = MyTask() t.run()

from multiprocessing.pool import ThreadPool as Pool class MyTask(object): def task(self, x): return x*x def run(self): pool = Pool(3) a = [1, 2, 3] ret = pool.map(self.task, a) print ret if __name__ == '__main__': t = MyTask() t.run()

import multiprocessing import types import copy_reg def _pickle_method(m): if m.im_self is None: return getattr, (m.im_class, m.im_func.func_name) else: return getattr, (m.im_self, m.im_func.func_name) copy_reg.pickle(types.MethodType, _pickle_method) class MyTask(object): def task(self, x): return x * x def run(self): pool = multiprocessing.Pool(processes=3) a = [1, 2, 3] ret = pool.map(self.task, a) print ret if __name__ == '__main__': t = MyTask() t.run()

import multiprocessing import types import copy_reg def _pickle_method(method): func_name = method.im_func.__name__ obj = method.im_self cls = method.im_class return _unpickle_method, (func_name, obj, cls) def _unpickle_method(func_name, obj, cls): for cls in cls.mro(): try: func = cls.__dict__[func_name] except KeyError: pass else: break return func.__get__(obj, cls) copy_reg.pickle(types.MethodType, _pickle_method, _unpickle_method) class MyTask(object): def task(self, x): return x * x def run(self): pool = multiprocessing.Pool(processes=3) a = [1, 2, 3] ret = pool.map(self.task, a) print ret if __name__ == '__main__': t = MyTask() t.run()

import multiprocessing def task(x): return x * x class MyTask(object): def run(self): pool = multiprocessing.Pool(processes=3) a = [1, 2, 3] ret = pool.map(task, a) print ret if __name__ == '__main__': t = MyTask() t.run() # [1, 4, 9]
multiprocessing.Pool类
在使用Python进行系统管理时,特别是同时操作多个文件目录或者远程控制多台主机,并行操作可以节约大量的时间。如果操作的对象数目不大时,还可以直接使用multiprocessing.Pool类动态的生成多个进程,十几个还好,但是如果上百个甚至更多,那手动去限制进程数量就显得特别的繁琐,此时进程池就派上用场了。
multiprocessing.Pool类可以提供指定数量的进程供用户调用,当有新的请求提交到multiprocessing.Pool中时,如果池还没有满,就会创建一个新的进程来执行请求。如果池满,请求就会告知先等待,直到池中有进程结束,才会创建新的进程来执行这些请求。
下面介绍一下multiprocessing 模块下的Pool类下的几个方法
apply()
函数原型:
apply(func[, args=()[, kwds={}]])
该函数用于传递不定参数,主进程会被阻塞直到函数执行结束(不建议使用,并且3.x以后不在出现)。
apply_async()
函数原型:
apply_async(func[, args=()[, kwds={}[, callback=None]]])
与apply用法一样,但它是非阻塞且支持结果返回进行回调。
#!/usr/bin/env python # -*- coding: utf-8 -*- import multiprocessing def task(x): print x + 1, x + 2 return x + 1, x + 2 class MyTask(object): def __init__(self): self.pool = multiprocessing.Pool(processes=20) def add(self,args): print args print "%s+%s" % (args[0], args[0]) def run1(self): for i in range(5): self.pool.apply_async(task, args=(i,), callback=self.add) # 回到函数 self.pool.close() self.pool.join() if __name__ == '__main__': t = MyTask() t.run1()
map()
函数原型:
map(func, iterable[, chunksize=None])
Pool类中的map方法,与内置的map函数用法行为基本一致,它会使进程阻塞直到返回结果。
注意,虽然第二个参数是一个迭代器,但在实际使用中,必须在整个队列都就绪后,程序才会运行子进程。
close()
关闭进程池(pool),使其不在接受新的任务。
terminate()
结束工作进程,不在处理未处理的任务。
join()
主进程阻塞等待子进程的退出,join方法必须在close或terminate之后使用。
multiprocessing.Pool类的实例:
import time import multiprocessing def run(fn): #fn: 函数参数是数据列表的一个元素 time.sleep(1) return fn*fn if __name__ == "__main__": testFL = [1,2,3,4,5,6] print 'shunxu:' #顺序执行(也就是串行执行,单进程) s = time.time() for fn in testFL: run(fn) e1 = time.time() print "顺序执行时间:", int(e1 - s) print 'concurrent:' #创建多个进程,并行执行 pool = multiprocessing.Pool(5) #创建拥有5个进程数量的进程池 #testFL:要处理的数据列表,run:处理testFL列表中数据的函数 rl =pool.map(run, testFL) pool.close()#关闭进程池,不再接受新的进程 pool.join()#主进程阻塞等待子进程的退出 e2 = time.time() print "并行执行时间:", int(e2-e1) print rl
执行结果:
shunxu: 顺序执行时间: 6 concurrent: 并行执行时间: 2 [1, 4, 9, 16, 25, 36]
上例是一个创建多个进程并发处理与顺序执行处理同一数据,所用时间的差别。从结果可以看出,并发执行的时间明显比顺序执行要快很多,但是进程是要耗资源的,所以平时工作中,进程数也不能开太大。
程序中的r1表示全部进程执行结束后全局的返回结果集,run函数有返回值,所以一个进程对应一个返回结果,这个结果存在一个列表中,也就是一个结果堆中,实际上是用了队列的原理,等待所有进程都执行完毕,就返回这个列表(列表的顺序不定)。
对Pool对象调用join()方法会等待所有子进程执行完毕,调用join()之前必须先调用close(),让其不再接受新的Process了。
再看一个实例:
import time import multiprocessing def run(fn) : time.sleep(2) print fn if __name__ == "__main__" : startTime = time.time() testFL = [1,2,3,4,5] pool = multiprocessing.Pool(10)#可以同时跑10个进程 pool.map(run,testFL) pool.close() pool.join() endTime = time.time() print "time :", endTime - startTime
执行结果:
21 3 4 5 time : 2.51999998093
结果中为什么还有空行和没有折行的数据呢?其实这跟进程调度有关,当有多个进程并行执行时,每个进程得到的时间片时间不一样,哪个进程接受哪个请求以及执行完成时间都是不定的,所以会出现输出乱序的情况。那为什么又会有没这行和空行的情况呢?因为有可能在执行第一个进程时,刚要打印换行符时,切换到另一个进程,这样就极有可能两个数字打印到同一行,并且再次切换回第一个进程时会打印一个换行符,所以就会出现空行的情况。
线程池,threading
import threadpool pool = threadpool.ThreadPool(10) #建立线程池,控制线程数量为10 reqs = threadpool.makeRequests(get_title, data, print_result) #构建请求,get_title为要运行的函数,data为要多线程执行函数的参数 #最后这个print_result是可选的,是对前两个函数运行结果的操作,回调函数 [pool.putRequest(req) for req in reqs] #多线程一块执行,相当于如下两行代码 #for req in requests: # pool.putRequest(req) pool.wait() #线程挂起,直到结束
它通过传入一个参数组来实现多线程,并且它的多线程是有序的,顺序与参数组中的参数顺序保持一致。
用它传递参数组的例子如下:
import threadpool def hello(m, n, o): """""" print "m = %s, n = %s, o = %s"%(m, n, o) if __name__ == '__main__': # 方法1 lst_vars_1 = ['1', '2', '3'] lst_vars_2 = ['4', '5', '6'] func_var = [(lst_vars_1, None), (lst_vars_2, None)] # 方法2 dict_vars_1 = {'m':'1', 'n':'2', 'o':'3'} dict_vars_2 = {'m':'4', 'n':'5', 'o':'6'} func_var = [(None, dict_vars_1), (None, dict_vars_2)] pool = threadpool.ThreadPool(2) requests = threadpool.makeRequests(hello, func_var) [pool.putRequest(req) for req in requests] pool.wait()
参考资料:
https://www.cnblogs.com/lovychen/p/5411743.html
http://gashero.yeax.com/?p=44#sys-exc-info