线程池,我的设计参考

来源:互联网 发布:网页数据采集工具 编辑:程序博客网 时间:2024/06/14 23:21

http://blog.csdn.net/zy825316/article/details/19305473

什么是线程池

虽然我自己对其有着一点理解,但是我还是决定直接翻译英文wiki:Thread pool pattern对其的讲解:

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.
在编程中,线程池是堆放了一些用于完成一系列任务的线程的地方。这些任务通常以队列的方式来组织,而且其这些任务产生的结果将诶放在另一个队列中,当然也可以没有返回,I比如当这个任务仅仅是用于展示动画。通常,任务的数量都会会远大于线程的数量,一旦一个线程完成了一个任务,它会去任务队列中再去取任务,直到所有的任务都被执行完毕了。之后这个线程可以终止,也可以休眠等待下一个任务的到来。

The number of threads used is a parameter that can be tuned to provide the best performance. Additionally, the number of threads can be dynamic based on the number of waiting tasks. For example, a web server can add threads if numerous web page requests come in and can remove threads when those requests taper down. The cost of having a larger thread pool is increased resource usage. The algorithm used to determine when to create or destroy threads will have an impact on the overall performance:
  • 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服务器接收到了大量请求的时候将会增加线程的数量,而当请求很少的时候就会减少线程的数量。由于维护一个拥有大量线程的线程池的代价非常高昂,会消耗很多的资源。那些能够决定线程创建或者销毁的算法将会对服务器的性能产生极大的影响。然而,下面几个基本的规则是值得注意的:

  • 创建了太多的线程,资源就会被浪费,而且会浪费时间去创建线程。
  • 销毁了太多的线程,当任务过多的时候,更多的时间就会用于创建线程。
  • 创建线程太慢,可能会导致提供给客户端糟糕的服务(长时间的等待)。
  • 销毁线程太慢,可能会影响别的进程使用资源。


The algorithm chosen will depend on the problem and the expected usage patterns.
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的一个,甚至多个事务。


When implementing this pattern, the programmer should ensure thread-safety of the queue. In Java, you can synchronize the relevant method using the synchronized keyword. This will bind the block modified with synchronized into one atomic structure, therefore forcing any threads using the associated resource to wait until there are no threads using the resource. As a drawback to this method, synchronization is rather expensive. You can also create an object that holds a list of all the jobs in a queue, which could be a singleton.

实现一个线程池,程序员首先应该注意队列的线程安全的问题。在java中,你可以使用使用synchronized keyword(同步锁)来完成一系列同步的问题。这将会绑定一个被synchronized 修饰的模块,使其进入一个原子结构,因此,所有其他想要使用这个资源的线程必须等到没有别的线程在使用这个资源的时候才能使用。然而遗憾的是,这个方法的代价十分高昂。你也可以创建一个单例的对象,它将所有的任务都装在一个队列之中。(前面资源就是这个队)


Typically, a thread pool executes on a single computer. However, thread pools are conceptually related to server farms in which a master process, which might be a thread pool itself, distributes tasks to worker processes on different computers, in order to increase the overall throughput. Embarrassingly parallel problems are highly amenable to this approach.

通常,一个线程池都在一个电脑上运行。然而,线程池通常在概念上有一个管理者,它也许就是线程池自身,它可以分配任务给在不同电脑上的工作线程,这是为了能够增加整体的吞吐量。尴尬的是,并行问题将会成为这个方法的瓶颈。



python代码

主要参考:对Python线程池进行详细说明

线程池代码中主要使用了两个类和一个方法,我已经添加了详细的注释:

  • Worker类,就是工作线程
  • WorkerManager类,管理工作线程的类
  • main()方法,python不是必须如此,我随意取的名,只是我用来处理逻辑的代码

代码如下:

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #-*- coding: utf-8 -*-  
  2.   
  3. from pylab import *  
  4. import MySQLdb  
  5. import copy  
  6. from random import randint  
  7. import operator#为了让字典以值排序  
  8. import Queue,sys  
  9. from threading import Thread  
  10.   
  11. # working thread,工作的线程  
  12. class Worker(Thread):  
  13.     worker_count = 0  
  14.     def __init__( self, workQueue, resultQueue, timeout = 0):  
  15.        Thread.__init__( self)  
  16.        self.id = Worker.worker_count  
  17.        Worker.worker_count += 1  
  18.        self.setDaemon( True ) #true表示创建该线程的线程结束时,会把这个子线程也杀死  
  19.        self.workQueue = workQueue#所有线程都会共享这个工作的队列,这个工作队列里面装着需要运行的函数和参数  
  20.        self.resultQueue = resultQueue#所有都会共享这个结果队列  
  21.        self.timeout = timeout#在我这里应该所没用的,但是这份完整的代码我已经运行了,所以暂时不删。  
  22.        self.start( )#这个工作线程一被创建就要求开始运行run()函数  
  23.   
  24.     def run( self ):  
  25.         ''''' the get-some-work, do-some-work main loop of worker threads '''  
  26.         while True:#线程一直没有死,循环执行这段代码  
  27.            try:  
  28.                callable, args = self.workQueue.get(timeout=self.timeout)#从工作队列中取出需要执行的函数和参数  
  29.                print "worker[%d]: %s %d" % (self.id,'正在计算收藏数为:',args[0])  
  30.                res = callable(*args)#执行取出的函数和参数  
  31.                self.resultQueue.put( res )#将结果放入到结果的队列里面  
  32.            except Queue.Empty:#如果去工作队列取函数和参数的时候,队列为空的话就执行这里  
  33.                break  
  34.            except :  
  35.                print 'worker[%2d]' % self.id, sys.exc_info()[:2]  
  36.   
  37. #管理工作线程的类  
  38. class WorkerManager:  
  39.     def __init__( self, num_of_workers=10, timeout = 1):  
  40.        self.workQueue = Queue.Queue()#创建工作队列  
  41.        self.resultQueue = Queue.Queue()#创建结果队列  
  42.        self.workers = []#用一个列表来装写线程对象  
  43.        self.timeout = timeout#应该没用  
  44.        self._recruitThreads( num_of_workers )#创建相应数量的新进程  
  45.   
  46.     #  
  47.     def _recruitThreads( self, num_of_workers ):  
  48.        for i in range( num_of_workers ):  
  49.            worker = Worker( self.workQueue, self.resultQueue, self.timeout )#创建好线程  
  50.            self.workers.append(worker)#将线程加入到一个列表中  
  51.   
  52.     #  
  53.     def wait_for_complete( self):  
  54.        # ...then, wait for each of them to terminate:  
  55.        #比如:只剩下两个任务了,那么就是有两个工作线程在工作这两个任务  
  56.        #第三个线程,取出来之后,发现已经运行完了,再去看看队列是否为空,为空,那么就不会再把自己添加到工作的线程组里面了  
  57.        #于是第四个/第五个,也是如此,那么self.workers=2,接着一个一个的结束。self.workers=0,工作队列的所有工作  
  58.        #都工作完了,那么就中止while循环  
  59.        while len(self.workers):  
  60.            worker = self.workers.pop()#取出一个工作线程  
  61.            worker.join( )#阻塞在此,线程结束后才往后走。这里并没有启动线程,线程一被创建就已经启动了。  
  62.            if worker.isAlive() and not self.workQueue.empty():#isAlive方法查看线程是否运行  
  63.                self.workers.append( worker )#如果还有工作队列,然而线程  
  64.        print "All jobs are are completed."  
  65.   
  66.     #加入工作队列,第二个参数是函数,第三个参数是函数的参数  
  67.     def add_job( self, callable, *args):  
  68.        self.workQueue.put( (callable, args) )#注意是按元组的方式添加进去的  
  69.   
  70.     #通过参数来获得从队列中获得特定的结果  
  71.     def get_result( self, *args):  
  72.        return self.resultQueue.get(*args)#没搞懂为什么可以这样  
  73.   
  74. def main():#习惯性的做法,并不是python语法的要求  
  75.     startfavorit=50  
  76.     maxfavorites=1167#这是我自己单独用sql语句查处来的,收藏歌曲最多的人数就所1165  
  77.     print 'start working'  
  78.     wm = WorkerManager(10)  
  79.     for i in range(startfavorit,maxfavorites,1):#逐渐把收藏数加1,然后传入到收藏队列中去  
  80.         wm.add_job( countaccuracy,i)#第一个为函数,第二个为该函数的参数  
  81.     wm.wait_for_complete()  
  82.     print 'end working'  


一份工作的代码,其实与线程池已经无关了,我贴在这里只是为了保证这次线程池代码例子的完整性,具体这次例子所用来干嘛的,请参阅博客:
[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. def countaccuracy(favorites):  
  2.     try:  
  3.         repeatCount=100#定义对每一个用户重复多少次,每一次都会重新选择出测试集和训练集,然后产生推荐列表,比对结果之类的  
  4.         #用数组列表,每一个装一个用户的。每个数组装一个字典。  
  5.         users=[]  
  6.         #下一句是连好数据库  
  7.         conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306,unix_socket='/opt/lampp/var/mysql/mysql.sock')  
  8.         #用拿到执行sql语句的对象  
  9.         cur1=conn.cursor()  
  10.         cur2=conn.cursor()  
  11.         count1=cur1.execute('SELECT userid,COUNT( userid )FROM moresmallfavorites GROUP BY userid having COUNT( userid )=%s ORDER by COUNT( userid )',[favorites])  
  12.         results1=cur1.fetchall()  
  13.         for r1 in results1:  
  14.             users.append({'userid':r1[0],'count':r1[1],'accuracy':0,'firstRecom':0})  
  15.         for i in range(len(users)):  
  16.             currentId=users[i]['userid']  
  17.             firstRecom=0#推荐列表排名第一的是否是被我删除了的用户已经收藏的歌曲  
  18.             accuracy=0#新的开始,使accuracy为0  
  19.             musics=[]  
  20.             #下句count1会记录返回了多少歌曲数  
  21.             count1=cur1.execute('SELECT musicid FROM moresmallfavorites where userid= %s',[currentId])  
  22.             results1=cur1.fetchall()  
  23.             for r1 in results1:  
  24.                 musics.append(r1[0])#就拿到了该用户收藏的歌  
  25.             #将收藏的歌曲集分为两部分:trainset和testSet,但先确定testSet的数量:testSetCount  
  26.             testSetCount=int(count1*0.1)  
  27.             if testSetCount<10:testSetCount=10  
  28.             print '正在计算用户id为',users[i]['userid'],'收藏了:',users[i]['count']  
  29.             for k in range(repeatCount):  
  30.                 simMusics={}#出现在这里非常重要,相当于将其每一个循环都要清空上一个循环的内容  
  31.                 testSet=[]#和上句的含义一样,之前我把这个写在最开头就导致了一个bug的出现,检查了很久才查出来  
  32.                 trainset=copy.deepcopy(musics)#这样,也相当于把清空了上一个循环的内容  
  33.                 for j in range(testSetCount):  
  34.                     deleteCount=randint(0,len(trainset)-1)#生成的随机数n:0<=n<=len(musics)  
  35.                     testSet.append(trainset[deleteCount])  
  36.                     trainset.pop(deleteCount)#删除指定位置的元素,那么trainset就变成了训练集  
  37.                 #取出musics里面所有的相似的歌,去重,排序,取出前10个  
  38.                 for j in range(len(trainset)):  
  39.                     count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])  
  40.                     results1=cur1.fetchall()  
  41.                     for r1 in results1:  
  42.                         sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就相加。  
  43.                         if sim!=r1[1]:  
  44.                             #simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对  
  45.                             simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'  
  46.                 #sortedSimMusics是一个以值降序排列的含二元元组的列表  
  47.                 sortedSimMusics=sorted(simMusics.iteritems(), key=operator.itemgetter(1),reverse=True)#这时候是降序的  
  48.                 for j in range(10):#我们只验证推荐列表里面的前10首歌是否属于测试集testSet  
  49.                     if sortedSimMusics[j][0in testSet:  
  50.                         accuracy+=1  
  51.                         if j==0:firstRecom+=1  
  52.             accuracy/=float(repeatCount*10)#除以重复的次数,由于repeatCount是int类,转换为float(有小数).乘以10表示转为概率:从10首里面有几首是,转换为 10首里面百分之几的概率是  
  53.             firstRecom/=float(repeatCount)  
  54.             users[i]['accuracy']=accuracy  
  55.             users[i]['firstRecom']=firstRecom  
  56.             count2=cur1.execute("INSERT INTO resultonlypearson(userid,musiccount,accuracy,firstRecom) VALUES (%s,%s,%s,%s)",\  
  57.                                 [users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']])  
  58.             #print i,users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']  
  59.             conn.commit()#必须要有这个提交事务,否则不能正确插入  
  60.         del users  
  61.         cur1.close()  
  62.         conn.close()  
  63.     except MySQLdb.Error,e:  
  64.         print "Mysql Error %d: %s" % (e.args[0], e.args[1])  

直接代码:
[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. main()  
就可以开始工作了。

其他要点

没搞懂的Queue的get方法

我做过实验,确实可以这样:
[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #通过参数来获得从队列中获得特定的结果  
  2. def get_result( self, *args):  
  3.    return self.resultQueue.get(*args)#没搞懂为什么可以这样  
但是让我们看放入队列的代码:
[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. self.resultQueue.put( res )#将结果放入到结果的队列里面  
到底所如何做到,传入一个参数就能获得特定的结果呢?
这个参数是传给工作队列的函数的参数。

Python的for循环

java/c/c++的写法:
[java] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for(i=50;i<100;i++)  
python的写法:
[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. for i in range(50,100,1):  
这样写就可以完成从50开始赋值给i,然后+1再赋值给i,然后+1再赋值给i,并且必须小于100,也就是最多取到99。

频繁访问数据库

当我使用线程池技术的时候,就有10个线程同时对数据库有着频繁的访问,如下面这句代码:
[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #取出musics里面所有的相似的歌,去重,排序,取出前10个  
  2.                for j in range(len(trainset)):  
  3.                    count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])  
  4.                    results1=cur1.fetchall()  
  5.                    for r1 in results1:  
  6.                        sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就相加。  
  7.                        if sim!=r1[1]:  
  8.                            #simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对  
  9.                            simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'  
所以只要cpu使用率主要在数据库的访问上,开始我还没相通这一点,后来师兄指点了一下我才想明白了,通过top命令来看进程如下图所示:

源代码

[python] view plaincopy在CODE上查看代码片派生到我的代码片
  1. #-*- coding: utf-8 -*-  
  2.   
  3. from pylab import *  
  4. import MySQLdb  
  5. import copy  
  6. from random import randint  
  7. import operator#为了让字典以值排序  
  8. import Queue,sys  
  9. from threading import Thread  
  10.   
  11. # working thread,工作的线程  
  12. class Worker(Thread):  
  13.     worker_count = 0  
  14.     def __init__( self, workQueue, resultQueue, timeout = 0):  
  15.        Thread.__init__( self)  
  16.        self.id = Worker.worker_count  
  17.        Worker.worker_count += 1  
  18.        self.setDaemon( True ) #true表示创建该线程的线程结束时,会把这个子线程也杀死  
  19.        self.workQueue = workQueue#所有线程都会共享这个工作的队列,这个工作队列里面装着需要运行的函数和参数  
  20.        self.resultQueue = resultQueue#所有都会共享这个结果队列  
  21.        self.timeout = timeout#在我这里应该所没用的,但是这份完整的代码我已经运行了,所以暂时不删。  
  22.        self.start( )#这个工作线程一被创建就要求开始运行run()函数  
  23.   
  24.     def run( self ):  
  25.         ''''' the get-some-work, do-some-work main loop of worker threads '''  
  26.         while True:#线程一直没有死,循环执行这段代码  
  27.            try:  
  28.                callable, args = self.workQueue.get(timeout=self.timeout)#从工作队列中取出需要执行的函数和参数  
  29.                print "worker[%d]: %s %d" % (self.id,'正在计算收藏数为:',args[0])  
  30.                res = callable(*args)#执行取出的函数和参数  
  31.                self.resultQueue.put( res )#将结果放入到结果的队列里面  
  32.            except Queue.Empty:#如果去工作队列取函数和参数的时候,队列为空的话就执行这里  
  33.                break  
  34.            except :  
  35.                print 'worker[%2d]' % self.id, sys.exc_info()[:2]  
  36.   
  37. #管理工作线程的类  
  38. class WorkerManager:  
  39.     def __init__( self, num_of_workers=10, timeout = 1):  
  40.        self.workQueue = Queue.Queue()#创建工作队列  
  41.        self.resultQueue = Queue.Queue()#创建结果队列  
  42.        self.workers = []#用一个列表来装写线程对象  
  43.        self.timeout = timeout#应该没用  
  44.        self._recruitThreads( num_of_workers )#创建相应数量的新进程  
  45.   
  46.     #  
  47.     def _recruitThreads( self, num_of_workers ):  
  48.        for i in range( num_of_workers ):  
  49.            worker = Worker( self.workQueue, self.resultQueue, self.timeout )#创建好线程  
  50.            self.workers.append(worker)#将线程加入到一个列表中  
  51.   
  52.     #  
  53.     def wait_for_complete( self):  
  54.        # ...then, wait for each of them to terminate:  
  55.        #比如:只剩下两个任务了,那么就是有两个工作线程在工作这两个任务  
  56.        #第三个线程,取出来之后,发现已经运行完了,再去看看队列是否为空,为空,那么就不会再把自己添加到工作的线程组里面了  
  57.        #于是第四个/第五个,也是如此,那么self.workers=2,接着一个一个的结束。self.workers=0,工作队列的所有工作  
  58.        #都工作完了,那么就中止while循环  
  59.        while len(self.workers):  
  60.            worker = self.workers.pop()#取出一个工作线程  
  61.            worker.join( )#阻塞在此,线程结束后才往后走。这里并没有启动线程,线程一被创建就已经启动了。  
  62.            if worker.isAlive() and not self.workQueue.empty():#isAlive方法查看线程是否运行  
  63.                self.workers.append( worker )#如果还有工作队列,然而线程  
  64.        print "All jobs are are completed."  
  65.   
  66.     #加入工作队列,第二个参数是函数,第三个参数是函数的参数  
  67.     def add_job( self, callable, *args):  
  68.        self.workQueue.put( (callable, args) )#注意是按元组的方式添加进去的  
  69.   
  70.     #通过参数来获得从队列中获得特定的结果  
  71.     def get_result( self, *args):  
  72.        return self.resultQueue.get(*args)#没搞懂为什么可以这样  
  73.   
  74. def main():#习惯性的做法,并不是python语法的要求  
  75.     startfavorit=50  
  76.     maxfavorites=1167#这是我自己单独用sql语句查处来的,收藏歌曲最多的人数就所1165  
  77.     print 'start working'  
  78.     wm = WorkerManager(10)  
  79.     for i in range(startfavorit,maxfavorites,1):#逐渐把收藏数加1,然后传入到收藏队列中去  
  80.         wm.add_job( countaccuracy,i)#第一个为函数,第二个为该函数的参数  
  81.     wm.wait_for_complete()  
  82.     print 'end working'  
  83.   
  84. def countaccuracy(favorites):  
  85.     try:  
  86.         repeatCount=100#定义对每一个用户重复多少次,每一次都会重新选择出测试集和训练集,然后产生推荐列表,比对结果之类的  
  87.         #用数组列表,每一个装一个用户的。每个数组装一个字典。  
  88.         users=[]  
  89.         #下一句是连好数据库  
  90.         conn=MySQLdb.connect(host='localhost',user='root',passwd='root',db='musicrecomsys',port=3306,unix_socket='/opt/lampp/var/mysql/mysql.sock')  
  91.         #用拿到执行sql语句的对象  
  92.         cur1=conn.cursor()  
  93.         cur2=conn.cursor()  
  94.         count1=cur1.execute('SELECT userid,COUNT( userid )FROM moresmallfavorites GROUP BY userid having COUNT( userid )=%s ORDER by COUNT( userid )',[favorites])  
  95.         results1=cur1.fetchall()  
  96.         for r1 in results1:  
  97.             users.append({'userid':r1[0],'count':r1[1],'accuracy':0,'firstRecom':0})  
  98.         for i in range(len(users)):  
  99.             currentId=users[i]['userid']  
  100.             firstRecom=0#推荐列表排名第一的是否是被我删除了的用户已经收藏的歌曲  
  101.             accuracy=0#新的开始,使accuracy为0  
  102.             musics=[]  
  103.             #下句count1会记录返回了多少歌曲数  
  104.             count1=cur1.execute('SELECT musicid FROM moresmallfavorites where userid= %s',[currentId])  
  105.             results1=cur1.fetchall()  
  106.             for r1 in results1:  
  107.                 musics.append(r1[0])#就拿到了该用户收藏的歌  
  108.             #将收藏的歌曲集分为两部分:trainset和testSet,但先确定testSet的数量:testSetCount  
  109.             testSetCount=int(count1*0.1)  
  110.             if testSetCount<10:testSetCount=10  
  111.             print '正在计算用户id为',users[i]['userid'],'收藏了:',users[i]['count']  
  112.             for k in range(repeatCount):  
  113.                 simMusics={}#出现在这里非常重要,相当于将其每一个循环都要清空上一个循环的内容  
  114.                 testSet=[]#和上句的含义一样,之前我把这个写在最开头就导致了一个bug的出现,检查了很久才查出来  
  115.                 trainset=copy.deepcopy(musics)#这样,也相当于把清空了上一个循环的内容  
  116.                 for j in range(testSetCount):  
  117.                     deleteCount=randint(0,len(trainset)-1)#生成的随机数n:0<=n<=len(musics)  
  118.                     testSet.append(trainset[deleteCount])  
  119.                     trainset.pop(deleteCount)#删除指定位置的元素,那么trainset就变成了训练集  
  120.                 #取出musics里面所有的相似的歌,去重,排序,取出前10个  
  121.                 for j in range(len(trainset)):  
  122.                     count1=cur1.execute(' SELECT simmusicid,similarity FROM simmusic where musicid=%s',[musics[j]])  
  123.                     results1=cur1.fetchall()  
  124.                     for r1 in results1:  
  125.                         sim=simMusics.setdefault(r1[0],r1[1])#就拿到了该用户收藏的歌,但是用户收藏的两首歌的相似度歌重复的话,那么就相加。  
  126.                         if sim!=r1[1]:  
  127.                             #simMusics['r1[0]']=r1[1]+sim#这句错误,不要这样写,因为会产生一个key为'r1[0]'的键值对  
  128.                             simMusics[r1[0]]=r1[1]+sim#键是r1[0]所引用的值,而不是'r1[0]'  
  129.                 #sortedSimMusics是一个以值降序排列的含二元元组的列表  
  130.                 sortedSimMusics=sorted(simMusics.iteritems(), key=operator.itemgetter(1),reverse=True)#这时候是降序的  
  131.                 for j in range(10):#我们只验证推荐列表里面的前10首歌是否属于测试集testSet  
  132.                     if sortedSimMusics[j][0in testSet:  
  133.                         accuracy+=1  
  134.                         if j==0:firstRecom+=1  
  135.             accuracy/=float(repeatCount*10)#除以重复的次数,由于repeatCount是int类,转换为float(有小数).乘以10表示转为概率:从10首里面有几首是,转换为 10首里面百分之几的概率是  
  136.             firstRecom/=float(repeatCount)  
  137.             users[i]['accuracy']=accuracy  
  138.             users[i]['firstRecom']=firstRecom  
  139.             count2=cur1.execute("INSERT INTO resultonlypearson(userid,musiccount,accuracy,firstRecom) VALUES (%s,%s,%s,%s)",\  
  140.                                 [users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']])  
  141.             #print i,users[i]['userid'],users[i]['count'],users[i]['accuracy'],users[i]['firstRecom']  
  142.             conn.commit()#必须要有这个提交事务,否则不能正确插入  
  143.         del users  
  144.         cur1.close()  
  145.         conn.close()  
  146.     except MySQLdb.Error,e:  
  147.         print "Mysql Error %d: %s" % (e.args[0], e.args[1])  
  148.   
  149. main()  

代码一份已经上传网盘:
  • threadPoolAndGetResultOnlyPearson.py

0 0