最近碰到个问题,需要telnet登录上千台机器去取主机名;其中有用户名密码交互部分,有需要延迟的部分,大概一次登录一次到处理完要10s,1000台机器串行处理就需要1000×10s,差不多三个小时,这是很难受的事情;
之前用thread的start_new_thread方法也可以实现,但是线程数量不好控制,没找到相关的控制线程数量的锁;
找了下关于Python的线程池,找到threadpool这么一个模块,可以满足我的需求,见:
http://chrisarndt.de/projects/threadpool/
我下的是版本1.2.2:
http://chrisarndt.de/projects/threadpool/download/threadpool-1.2.2.tar.bz2
放到当前目录或者python模块库都行,用法很简单,见:
第一行定义了一个线程池,表示最多可以创建poolsize这么多线程;
第二行是调用makeRequests创建了要开启多线程的函数,以及函数相关参数和回调函数,其中回调函数可以不写,default是无,也就是说makeRequests只需要2个参数就可以运行;
第三行用法比较奇怪,是将所有要运行多线程的请求扔进线程池,[pool.putRequest(req) for req in requests]等同于:
第四行是等待所有的线程完成工作后退出;
下面看下我的代码,使用线程池前后代码对比,不使用线程池:
如果myTelnet每次执行要10s,那么255次myTelnet就需要2550s,大概是40分钟;
用多线程的情况:
开始是个线程,理论上应该快10倍,实际可能没这么快,我将myTelnet函数改成只的sleep 10秒,什么也不干,测了下执行完需要260s,几乎是10倍的速度;改成如下:
90s执行完毕,说明线程池还是很有用的东西。
自己实现:
最近在做一些文本处理方面的事情,考虑到程序利用并发性可以提高执行效率(不纠结特殊反例),于是入围的Idea如使用多进程或多线程达到期望的目标,对于进程或线程的创建是有代价的,那么我们是否可以实现一个线程池来达到已创建的线程反复使用从而使代价降低到最小呢?
当然可以,要想创建一个线程池,那么必须得有个容器来模拟“池”,在Python中,队列这样的数据结构就可以帮我们解决“池”这个问题,然而随之引来的 多线程之间数据处理同步问题,好在Python中有个Queue模块帮我们解决了这一棘手的问题,那么我们就可以实现一个线程池的雏形了。
- # !/usr/bin/env python
- # -*- coding:utf-8 -*-
- import Queue
- import threading
- import time
- class WorkManager(object):
- def __init__(self, work_num=1000,thread_num=2):
- self.work_queue = Queue.Queue()
- self.threads = []
- self.__init_work_queue(work_num)
- self.__init_thread_pool(thread_num)
- """
- 初始化线程
- """
- def __init_thread_pool(self,thread_num):
- for i in range(thread_num):
- self.threads.append(Work(self.work_queue))
- """
- 初始化工作队列
- """
- def __init_work_queue(self, jobs_num):
- for i in range(jobs_num):
- self.add_job(do_job, i)
- """
- 添加一项工作入队
- """
- def add_job(self, func, *args):
- self.work_queue.put((func, list(args)))#任务入队,Queue内部实现了同步机制
- """
- 等待所有线程运行完毕
- """
- def wait_allcomplete(self):
- for item in self.threads:
- if item.isAlive():item.join()
- class Work(threading.Thread):
- def __init__(self, work_queue):
- threading.Thread.__init__(self)
- self.work_queue = work_queue
- self.start()
- def run(self):
- #死循环,从而让创建的线程在一定条件下关闭退出
- while True:
- try:
- do, args = self.work_queue.get(block=False)#任务异步出队,Queue内部实现了同步机制
- do(args)
- self.work_queue.task_done()#通知系统任务完成
- except:
- break
- #具体要做的任务
- def do_job(args):
- time.sleep(0.1)#模拟处理时间
- print threading.current_thread(), list(args)
- if __name__ == '__main__':
- start = time.time()
- work_manager = WorkManager(10000, 10)#或者work_manager = WorkManager(10000, 20)
- work_manager.wait_allcomplete()
- end = time.time()
- print "cost all time: %s" % (end-start)
2次开启不同的线程数运行结果如下:
- #work_manager = WorkManager(10000, 10)
- cost all time: 100.641790867(单位:秒)
- #work_manager = WorkManager(10000, 20)
- cost all time:50.5233478546(单位:秒)
上面实现了线程池的雏形,展现了基本原理,当然要想成为通用的API需要做很多的工作,希望本文能够起到抛砖引玉的效果。
threadpool例子:
1 #!/usr/bin/env python
2 #coding=gbk
3 import threadpool
4 import time,random
5
6 def hello(str):
7 time.sleep(2)
8 s = "world"
9 return str, s
10
11 def print_result(request, result):
12 print "the result is %s %r, and %s" % (request.requestID, result[0],result[1])
13
14 if __name__ == '__main__':
15 data = [random.randint(1,10) for i in range(20)]
16 print data
17 pool = threadpool.ThreadPool(5)
18 requests = threadpool.makeRequests(hello, [1,], print_result)
19 #requests = threadpool.makeRequests(hello, data, print_result)
20 [pool.putRequest(req) for req in requests]
21 pool.wait()
#!/usr/bin/env python
2 #coding=gbk
3 import threadpool
4 import time,random
5
6 def hello(str,st):
7 time.sleep(2)
8 s = "world"
9 return str, st, s
10
11 def print_result(request, result):
12 print "the result is %s %r, and %s, %s" % (request.requestID, result[0],result[1] ,result[2])
13
14 if __name__ == '__main__':
15 #data = [random.randint(1,10) for i in range(20)]
16 #print data
17 var1 = [1,2]
18 var2 = [2,4]
19 func_p = [(var1,None),(var2,None)]
20 pool = threadpool.ThreadPool(5)
21 requests = threadpool.makeRequests(hello, func_p, print_result)
22 #requests = threadpool.makeRequests(hello, data, print_result)
23 [pool.putRequest(req) for req in requests]
24 pool.wait()