http://blog.csdn.net/zy825316/article/details/19305473
什么是线程池
In computer programming, the thread pool pattern (also replicated workers or worker-crew model[1]) is where a number of threads are created to perform a number of tasks, which are usually organized in a queue. The results from the tasks being executed might also be placed in a queue, or the tasks might return no result (for example, if the task is for animation). Typically, there are many more tasks than threads. As soon as a thread completes its task, it will request the next task from the queue until all tasks have been completed. The thread can then terminate, or sleep until there are new tasks available.
- create too many threads, and resources are wasted and time also wasted creating any unused threads
- destroy too many threads and more time will be spent later creating them again
- creating threads too slowly might result in poor client performance (long wait times)
- destroying threads too slowly may starve other processes of resources
这些被使用的线程有一系列可以供调整的参数,因此能够带来更好的性能表现。此外,线程的数量也是可以根据任务的数量来进行调整的。比如,当一个web服务器接收到了大量请求的时候将会增加线程的数量,而当请求很少的时候就会减少线程的数量。由于维护一个拥有大量线程的线程池的代价非常高昂,会消耗很多的资源。那些能够决定线程创建或者销毁的算法将会对服务器的性能产生极大的影响。然而,下面几个基本的规则是值得注意的:
- 创建了太多的线程,资源就会被浪费,而且会浪费时间去创建线程。
- 销毁了太多的线程,当任务过多的时候,更多的时间就会用于创建线程。
- 创建线程太慢,可能会导致提供给客户端糟糕的服务(长时间的等待)。
- 销毁线程太慢,可能会影响别的进程使用资源。
If the number of tasks is very large, then creating a thread for each one may be impractical.
Another advantage of using a thread pool over creating a new thread for each task is thread creation and destruction overhead is negated, which may result in better performance and better system stability. Creating and destroying a thread and its associated resources is an expensive process in terms of time. An excessive number of threads will also waste memory, and context-switching between the runnable threads also damages performance. For example, a socket connection to another machine—which might take thousands (or even millions) of cycles to drop and re-establish—can be avoided by associating it with a thread which lives over the course of more than one transaction.
具体算法的选择将会依据具体问题和期待的资源使用的方式。如果任务的数量巨大的话,为每一个任务都创建一个进程的话是不切实际的。另一个使用线程池而不是为每一个线程都创建一个任务的优势就是经常性的创建线程和销毁线程是无效的,线程池还会使得更好的性能表现和系统稳定。创建和销毁一个线程及其资源的过程是非常费时的,过多的线程将会浪费很多的内存,而且频繁在多个线程之间切换,其代价又昂高,又会极其影响性能。I比如,一个socket的连接,这个过程可能会有数千次甚至数百万次断开链接和重建连接的过程,如果每一个断开和重建链接都伴随着一次线程的创建和销毁的话,性能极度底下,使用线程池我们就可以使用一个完成所有与这个socket的一个,甚至多个事务。
实现一个线程池,程序员首先应该注意队列的线程安全的问题。在java中,你可以使用使用synchronized keyword(同步锁)来完成一系列同步的问题。这将会绑定一个被synchronized 修饰的模块,使其进入一个原子结构,因此,所有其他想要使用这个资源的线程必须等到没有别的线程在使用这个资源的时候才能使用。然而遗憾的是,这个方法的代价十分高昂。你也可以创建一个单例的对象,它将所有的任务都装在一个队列之中。(前面资源就是这个队)
通常,一个线程池都在一个电脑上运行。然而,线程池通常在概念上有一个管理者,它也许就是线程池自身,它可以分配任务给在不同电脑上的工作线程,这是为了能够增加整体的吞吐量。尴尬的是,并行问题将会成为这个方法的瓶颈。
python代码
主要参考: 对Python线程池进行详细说明
线程池代码中主要使用了两个类和一个方法,我已经添加了详细的注释:
- Worker类,就是工作线程
- WorkerManager类,管理工作线程的类
- main()方法,python不是必须如此,我随意取的名,只是我用来处理逻辑的代码
代码如下:
- #-*- coding: utf-8 -*-
- from pylab import *
- import MySQLdb
- import copy
- from random import randint
- import operator#为了让字典以值排序
- import Queue,sys
- from threading import Thread
- # working thread,工作的线程
- class Worker(Thread):
- worker_count = 0
- def __init__( self, workQueue, resultQueue, timeout = 0):
- Thread.__init__( self)
- self.id = Worker.worker_count
- Worker.worker_count += 1
- self.setDaemon( True ) #true表示创建该线程的线程结束时,会把这个子线程也杀死
- self.workQueue = workQueue#所有线程都会共享这个工作的队列,这个工作队列里面装着需要运行的函数和参数
- self.resultQueue = resultQueue#所有都会共享这个结果队列
- self.timeout = timeout#在我这里应该所没用的,但是这份完整的代码我已经运行了,所以暂时不删。
- self.start( )#这个工作线程一被创建就要求开始运行run()函数
- def run( self ):
- ''''' the get-some-work, do-some-work main loop of worker threads '''
- while True:#线程一直没有死,循环执行这段代码
- try:
- callable, args = self.workQueue.get(timeout=self.timeout)#从工作队列中取出需要执行的函数和参数
- print "worker[%d]: %s %d" % (self.id,'正在计算收藏数为:',args[0])
- res = callable(*args)#执行取出的函数和参数
- self.resultQueue.put( res )#将结果放入到结果的队列里面
- except Queue.Empty:#如果去工作队列取函数和参数的时候,队列为空的话就执行这里
- break
- except :
- print 'worker[%2d]' % self.id, sys.exc_info()[:2]
- #管理工作线程的类
- class WorkerManager:
- def __init__( self, num_of_workers=10, timeout = 1):
- self.workQueue = Queue.Queue()#创建工作队列
- self.resultQueue = Queue.Queue()#创建结果队列
- self.workers = []#用一个列表来装写线程对象
- self.timeout = timeout#应该没用
- self._recruitThreads( num_of_workers )#创建相应数量的新进程
- #
- def _recruitThreads( self, num_of_workers ):
- for i in range( num_of_workers ):
- worker = Worker( self.workQueue, self.resultQueue, self.timeout )#创建好线程
- self.workers.append(worker)#将线程加入到一个列表中
- #
- def wait_for_complete( self):
- # ...then, wait for each of them to terminate:
- #比如:只剩下两个任务了,那么就是有两个工作线程在工作这两个任务
- #第三个线程,取出来之后,发现已经运行完了,再去看看队列是否为空,为空,那么就不会再把自己添加到工作的线程组里面了
- #于是第四个/第五个,也是如此,那么self.workers=2,接着一个一个的结束。self.workers=0,工作队列的所有工作
- #都工作完了,那么就中止while循环
- while len(self.workers):
- worker = self.workers.pop()#取出一个工作线程
- worker.join( )#阻塞在此,线程结束后才往后走。这里并没有启动线程,线程一被创建就已经启动了。
- if worker.isAlive() and not self.workQueue.empty():#isAlive方法查看线程是否运行
- self.workers.append( worker )#如果还有工作队列,然而线程
- print "All jobs are are completed."
- #加入工作队列,第二个参数是函数,第三个参数是函数的参数
- def add_job( self, callable, *args):
- self.workQueue.put( (callable, args) )#注意是按元组的方式添加进去的
- #通过参数来获得从队列中获得特定的结果
- def get_result( self, *args):
- return self.resultQueue.get(*args)#没搞懂为什么可以这样
- def main():#习惯性的做法,并不是python语法的要求
- startfavorit=50
- maxfavorites=1167#这是我自己单独用sql语句查处来的,收藏歌曲最多的人数就所1165
- print 'start working'
- wm = WorkerManager(10)
- for i in range(startfavorit,maxfavorites,1):#逐渐把收藏数加1,然后传入到收藏队列中去
- wm.add_job( countaccuracy,i)#第一个为函数,第二个为该函数的参数
- wm.wait_for_complete()
- print 'end working'
- def countaccuracy(favorites):
- try:
- repeatCount=100#定义对每一个用户重复多少次,每一次都会重新选择出测试集和训练集,然后产生推荐列表,比对结果之类的
- #用数组列表,每一个装一个用户的。每个数组装一个字典。
- users=[]
- #下一句是连好数据库
- conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306,unix_socket='/opt/lampp/var/mysql/mysql.sock')
- #用拿到执行sql语句的对象
- cur1=conn.cursor()
- cur2=conn.cursor()
- count1=cur1.execute('SELECT userid,COUNT( userid )FROM moresmallfavorites GROUP BY userid having COUNT( userid )=%s ORDER by COUNT( userid )',[favorites])
- results1=cur1.fetchall()
- for r1 in results1:
- users.append({'userid':r1[0],'count':r1[1],'accuracy':0,'firstRecom':0})
- for i in range(len(users)):
- currentId=users[i]['userid']
- firstRecom=0#推荐列表排名第一的是否是被我删除了的用户已经收藏的歌曲
- accuracy=0#新的开始,使accuracy为0
- musics=[]
- #下句count1会记录返回了多少歌曲数
- count1=cur1.execute('SELECT musicid FROM moresmallfavorites where userid= %s',[currentId])
- results1=cur1.fetchall()
- for r1 in results1:
- musics.append(r1[0])#就拿到了该用户收藏的歌
- #将收藏的歌曲集分为两部分:trainset和testSet,但先确定testSet的数量:testSetCount
- testSetCount=int(count1*0.1)
- if testSetCount<10:testSetCount=10
- print '正在计算用户id为',users[i]['userid'],'收藏了:',users[i]['count']
- for k in range(repeatCount):
- simMusics={}#出现在这里非常重要,相当于将其每一个循环都要清空上一个循环的内容
- testSet=[]#和上句的含义一样,之前我把这个写在最开头就导致了一个bug的出现,检查了很久才查出来
- trainset=copy.deepcopy(musics)#这样,也相当于把清空了上一个循环的内容
- for j in range(testSetCount):
- deleteCount=randint(0,len(trainset)-1)#生成的随机数n:0<=n<=len(musics)
- testSet.append(trainset[deleteCount])
- trainset.pop(deleteCount)#删除指定位置的元素,那么trainset就变成了训练集
- #取出musics里面所有的相似的歌,去重,排序,取出前10个
- for j in range(len(trainset)):
- count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])
- results1=cur1.fetchall()
- for r1 in results1:
- sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就相加。
- if sim!=r1[1]:
- #simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对
- simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'
- #sortedSimMusics是一个以值降序排列的含二元元组的列表
- sortedSimMusics=sorted(simMusics.iteritems(), key=operator.itemgetter(1),reverse=True)#这时候是降序的
- for j in range(10):#我们只验证推荐列表里面的前10首歌是否属于测试集testSet
- if sortedSimMusics[j][0] in testSet:
- accuracy+=1
- if j==0:firstRecom+=1
- accuracy/=float(repeatCount*10)#除以重复的次数,由于repeatCount是int类,转换为float(有小数).乘以10表示转为概率:从10首里面有几首是,转换为 10首里面百分之几的概率是
- firstRecom/=float(repeatCount)
- users[i]['accuracy']=accuracy
- users[i]['firstRecom']=firstRecom
- count2=cur1.execute("INSERT INTO resultonlypearson(userid,musiccount,accuracy,firstRecom) VALUES (%s,%s,%s,%s)",\
- [users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']])
- #print i,users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']
- conn.commit()#必须要有这个提交事务,否则不能正确插入
- del users
- cur1.close()
- conn.close()
- except MySQLdb.Error,e:
- print "Mysql Error %d: %s" % (e.args[0], e.args[1])
直接代码:
- main()
其他要点
没搞懂的Queue的get方法
- #通过参数来获得从队列中获得特定的结果
- def get_result( self, *args):
- return self.resultQueue.get(*args)#没搞懂为什么可以这样
- self.resultQueue.put( res )#将结果放入到结果的队列里面
这个参数是传给工作队列的函数的参数。
Python的for循环
- for(i=50;i<100;i++)
- for i in range(50,100,1):
频繁访问数据库
- #取出musics里面所有的相似的歌,去重,排序,取出前10个
- for j in range(len(trainset)):
- count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])
- results1=cur1.fetchall()
- for r1 in results1:
- sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就相加。
- if sim!=r1[1]:
- #simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对
- simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'
源代码
- #-*- coding: utf-8 -*-
- from pylab import *
- import MySQLdb
- import copy
- from random import randint
- import operator#为了让字典以值排序
- import Queue,sys
- from threading import Thread
- # working thread,工作的线程
- class Worker(Thread):
- worker_count = 0
- def __init__( self, workQueue, resultQueue, timeout = 0):
- Thread.__init__( self)
- self.id = Worker.worker_count
- Worker.worker_count += 1
- self.setDaemon( True ) #true表示创建该线程的线程结束时,会把这个子线程也杀死
- self.workQueue = workQueue#所有线程都会共享这个工作的队列,这个工作队列里面装着需要运行的函数和参数
- self.resultQueue = resultQueue#所有都会共享这个结果队列
- self.timeout = timeout#在我这里应该所没用的,但是这份完整的代码我已经运行了,所以暂时不删。
- self.start( )#这个工作线程一被创建就要求开始运行run()函数
- def run( self ):
- ''''' the get-some-work, do-some-work main loop of worker threads '''
- while True:#线程一直没有死,循环执行这段代码
- try:
- callable, args = self.workQueue.get(timeout=self.timeout)#从工作队列中取出需要执行的函数和参数
- print "worker[%d]: %s %d" % (self.id,'正在计算收藏数为:',args[0])
- res = callable(*args)#执行取出的函数和参数
- self.resultQueue.put( res )#将结果放入到结果的队列里面
- except Queue.Empty:#如果去工作队列取函数和参数的时候,队列为空的话就执行这里
- break
- except :
- print 'worker[%2d]' % self.id, sys.exc_info()[:2]
- #管理工作线程的类
- class WorkerManager:
- def __init__( self, num_of_workers=10, timeout = 1):
- self.workQueue = Queue.Queue()#创建工作队列
- self.resultQueue = Queue.Queue()#创建结果队列
- self.workers = []#用一个列表来装写线程对象
- self.timeout = timeout#应该没用
- self._recruitThreads( num_of_workers )#创建相应数量的新进程
- #
- def _recruitThreads( self, num_of_workers ):
- for i in range( num_of_workers ):
- worker = Worker( self.workQueue, self.resultQueue, self.timeout )#创建好线程
- self.workers.append(worker)#将线程加入到一个列表中
- #
- def wait_for_complete( self):
- # ...then, wait for each of them to terminate:
- #比如:只剩下两个任务了,那么就是有两个工作线程在工作这两个任务
- #第三个线程,取出来之后,发现已经运行完了,再去看看队列是否为空,为空,那么就不会再把自己添加到工作的线程组里面了
- #于是第四个/第五个,也是如此,那么self.workers=2,接着一个一个的结束。self.workers=0,工作队列的所有工作
- #都工作完了,那么就中止while循环
- while len(self.workers):
- worker = self.workers.pop()#取出一个工作线程
- worker.join( )#阻塞在此,线程结束后才往后走。这里并没有启动线程,线程一被创建就已经启动了。
- if worker.isAlive() and not self.workQueue.empty():#isAlive方法查看线程是否运行
- self.workers.append( worker )#如果还有工作队列,然而线程
- print "All jobs are are completed."
- #加入工作队列,第二个参数是函数,第三个参数是函数的参数
- def add_job( self, callable, *args):
- self.workQueue.put( (callable, args) )#注意是按元组的方式添加进去的
- #通过参数来获得从队列中获得特定的结果
- def get_result( self, *args):
- return self.resultQueue.get(*args)#没搞懂为什么可以这样
- def main():#习惯性的做法,并不是python语法的要求
- startfavorit=50
- maxfavorites=1167#这是我自己单独用sql语句查处来的,收藏歌曲最多的人数就所1165
- print 'start working'
- wm = WorkerManager(10)
- for i in range(startfavorit,maxfavorites,1):#逐渐把收藏数加1,然后传入到收藏队列中去
- wm.add_job( countaccuracy,i)#第一个为函数,第二个为该函数的参数
- wm.wait_for_complete()
- print 'end working'
- def countaccuracy(favorites):
- try:
- repeatCount=100#定义对每一个用户重复多少次,每一次都会重新选择出测试集和训练集,然后产生推荐列表,比对结果之类的
- #用数组列表,每一个装一个用户的。每个数组装一个字典。
- users=[]
- #下一句是连好数据库
- conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306,unix_socket='/opt/lampp/var/mysql/mysql.sock')
- #用拿到执行sql语句的对象
- cur1=conn.cursor()
- cur2=conn.cursor()
- count1=cur1.execute('SELECT userid,COUNT( userid )FROM moresmallfavorites GROUP BY userid having COUNT( userid )=%s ORDER by COUNT( userid )',[favorites])
- results1=cur1.fetchall()
- for r1 in results1:
- users.append({'userid':r1[0],'count':r1[1],'accuracy':0,'firstRecom':0})
- for i in range(len(users)):
- currentId=users[i]['userid']
- firstRecom=0#推荐列表排名第一的是否是被我删除了的用户已经收藏的歌曲
- accuracy=0#新的开始,使accuracy为0
- musics=[]
- #下句count1会记录返回了多少歌曲数
- count1=cur1.execute('SELECT musicid FROM moresmallfavorites where userid= %s',[currentId])
- results1=cur1.fetchall()
- for r1 in results1:
- musics.append(r1[0])#就拿到了该用户收藏的歌
- #将收藏的歌曲集分为两部分:trainset和testSet,但先确定testSet的数量:testSetCount
- testSetCount=int(count1*0.1)
- if testSetCount<10:testSetCount=10
- print '正在计算用户id为',users[i]['userid'],'收藏了:',users[i]['count']
- for k in range(repeatCount):
- simMusics={}#出现在这里非常重要,相当于将其每一个循环都要清空上一个循环的内容
- testSet=[]#和上句的含义一样,之前我把这个写在最开头就导致了一个bug的出现,检查了很久才查出来
- trainset=copy.deepcopy(musics)#这样,也相当于把清空了上一个循环的内容
- for j in range(testSetCount):
- deleteCount=randint(0,len(trainset)-1)#生成的随机数n:0<=n<=len(musics)
- testSet.append(trainset[deleteCount])
- trainset.pop(deleteCount)#删除指定位置的元素,那么trainset就变成了训练集
- #取出musics里面所有的相似的歌,去重,排序,取出前10个
- for j in range(len(trainset)):
- count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])
- results1=cur1.fetchall()
- for r1 in results1:
- sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就相加。
- if sim!=r1[1]:
- #simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对
- simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'
- #sortedSimMusics是一个以值降序排列的含二元元组的列表
- sortedSimMusics=sorted(simMusics.iteritems(), key=operator.itemgetter(1),reverse=True)#这时候是降序的
- for j in range(10):#我们只验证推荐列表里面的前10首歌是否属于测试集testSet
- if sortedSimMusics[j][0] in testSet:
- accuracy+=1
- if j==0:firstRecom+=1
- accuracy/=float(repeatCount*10)#除以重复的次数,由于repeatCount是int类,转换为float(有小数).乘以10表示转为概率:从10首里面有几首是,转换为 10首里面百分之几的概率是
- firstRecom/=float(repeatCount)
- users[i]['accuracy']=accuracy
- users[i]['firstRecom']=firstRecom
- count2=cur1.execute("INSERT INTO resultonlypearson(userid,musiccount,accuracy,firstRecom) VALUES (%s,%s,%s,%s)",\
- [users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']])
- #print i,users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']
- conn.commit()#必须要有这个提交事务,否则不能正确插入
- del users
- cur1.close()
- conn.close()
- except MySQLdb.Error,e:
- print "Mysql Error %d: %s" % (e.args[0], e.args[1])
- main()
- threadPoolAndGetResultOnlyPearson.py