2006年5月31日

Python多线程编程[Zz]


Python线程编程(一)线程对象



我们在做软件开发的时候很多要用到多线程技术。例如如果做一个下载软件象flashget就要用到、象在线视频工具realplayer也要用到因为要同时下载media stream还要播放。其实例子是很多的。

线程相对进程来说是“轻量级”的,操作系统用较少的资源创建和管理线程。程序中的线程在相同的内存空间中执行,并共享许多相同的资源。

在python中如何创建一个线程对象

如果你要创建一个线程对象,很简单,只要你的类继承threading.Thread,然后在__init__里首先调用threading.Thread的__init__方法即可

import threading
class mythread(threading.Thread):
def __init__(self, threadname):
threading.Thread.__init__(self, name = threadname)
....

这才仅仅是个空线程,我可不是要他拉空车的,他可得给我干点实在活。很简单,重写类的run()方法即可,把你要在线程执行时做的事情都放到里面

import threading
import time
class mythread(threading.Thread):
def __init__(...):
....
def run(self):
for i in range(10):
print self.getName, i
time.sleep(1)

以上代码我们让这个线程在执行之后每隔1秒输出一次信息到屏幕,10次后结束

getName()是threading.Thread类的一个方法,用来获得这个线程对象的name。还有一个方法setName()当然就是来设置这个线程对象的name的了。

如果要创建一个线程,首先就要先创建一个线程对象

mythread1 = mythread('mythread 1')

一个线程对象被创建后,他就处于“born”(诞生状态)

如何让这个线程对象开始运行呢?只要调用线程对象的start()方法即可

mythread1.start()

现在线程就处于“ready”状态或者也称为“runnable”状态。

奇怪吗?不是已经start了吗?为什么不称为“running”状态呢?其实是有原因的。因为我们的计算机一般是不具有真正并行处理能力的。我们所谓的多线程只是把时间分成片段,然后隔一个时间段就让一个线程执行一下,然后进入“sleeping ”状态,然后唤醒另一个在“sleeping”的线程,如此循环runnable->sleeping->runnable... ,只是因为计算机执行速度很快,而时间片段间隔很小,我们感受不到,以为是同时进行的。所以说一个线程在start了之后只是处在了可以运行的状态,他什么时候运行还是由系统来进行调度的。

那一个线程什么时候会“dead”呢?一般来说当线程对象的run方法执行结束或者在执行中抛出异常的话,那么这个线程就会结束了。系统会自动对“dead”状态线程进行清理。

如果一个线程t1在执行的过程中需要等待另一个线程t2执行结束后才能运行的话那就可以在t1在调用t2的join()方法

....
def t1(...):
...
t2.join()
...

这样t1在执行到t2.join()语句后就会等待t2结束后才会继续运行。

但是假如t1是个死循环的话那么等待就没有意义了,那怎么办呢?可以在调用t2的join()方法的时候给一个浮点数做超时参数,这样这个线程就不会等到花儿也谢了了。我等你10s,你不回来我还不允许我改嫁啊?:)

def t1(...):
...
t2.join(10)
...

如果一个进程的主线程运行完毕而子线程还在执行的话,那么进程就不会退出,直到所有子线程结束为止,如何让主线程结束的时候其他子线程也乖乖的跟老大撤退呢?那就要把那些不听话的人设置为听话的小弟,使用线程对象的setDaemon()方法,参数为bool型。True的话就代表你要听话,我老大(主线程)扯呼,你也要跟着撤,不能拖后腿。如果是False的话就不用那么听话了,老大允许你们将在外军命有所不受的。需要注意的是setDaemon()方法必须在线程对象没有调用start()方法之前调用,否则没效果。

t1 = mythread('t1')
print t1.getName(),t1.isDaemon()
t1.setDaemon(True)
print t1.getName(),t1.isDaemon()
t1.start()
print 'main thread exit'

当执行到 print 'main thread exit' 后,主线程就退出了,当然t1这个线程也跟着结束了。但是如果不使用t1线程对象的setDaemon()方法的话,即便主线程结束了,还要等待t1线程自己结束才能退出进程。isDaemon()是用来获得一个线程对象的Daemonflag状态的。

如何来获得与线程有关的信息呢?

获得当前正在运行的线程的引用

running = threading.currentThread()

获得当前所有活动对象(即run方法开始但是未终止的任何线程)的一个列表

threadlist = threading.enumerate()

获得这个列表的长度

threadcount = threading.activeCount()

查看一个线程对象的状态调用这个线程对象的isAlive()方法,返回1代表处于“runnable”状态且没有“dead”

threadflag = threading.isAlive()








Python线程编程(二)简单的线程同步



    多个执行线程经常要共享数据,如果仅仅读取共享数据还好,但是如果多个线程要修改共享数据的话就可能出现无法预料的结果。

    假如两个线程对象t1和t2都要对数值num=0进行增1运算,那么t1和t2都各对num修改10次的话,那么num最终的结果应该为20。但是如果当t1取得num的值时(假如此时num为0),系统把t1调度为“sleeping”状态,而此时t2转换为“running”状态,此时t2获得的num的值也为0,然后他把num+1的值1赋给num。系统又把t2转化为“sleeping”状态,t1为“running”状态,由于t1已经得到num值为0,所以他也把num+1的值赋给了num为1。本来是2次增1运行,结果却是num只增了1次。类似这样的情况在多线程同时执行的时候是有可能发生的。所以为了防止这类情况的出现就要使用线程同步机制。

    最简单的同步机制就是“锁”

    锁对象用threading.RLock类创建

    mylock = threading.RLock()

    如何使用锁来同步线程呢?线程可以使用锁的acquire() (获得)方法,这样锁就进入“locked”状态。每次只有一个线程可以获得锁。如果当另一个线程试图获得这个锁的时候,就会被系统变为“blocked”状态,直到那个拥有锁的线程调用锁的release() (释放)方法,这样锁就会进入“unlocked”状态。“blocked”状态的线程就会收到一个通知,并有权利获得锁。如果多个线程处于“blocked”状态,所有线程都会先解除“blocked”状态,然后系统选择一个线程来获得锁,其他的线程继续沉默(“blocked”)。

import threading
mylock = threading.RLock()
class mythread(threading.Thread)
    ...
    def run(self ...):
        ...     #此处 不可以 放置修改共享数据的代码
        mylock.acquire()
        ...     #此处 可以 放置修改共享数据的代码
        mylock.release()
        ...    
#此处 不可以 放置修改共享数据的代码

    我们把修改共享数据的代码称为“临界区”,必须将所有“临界区”都封闭在同一锁对象的acquire()和release()方法调用之间。

    锁只能提供最基本的同步级别。有时需要更复杂的线程同步,例如只在发生某些事件时才访问一个临界区(例如当某个数值改变时)。这就要使用“条件变量”。

    条件变量用threading.Condition类创建

    mycondition = threading.Condition()

    条件变量是如何工作的呢?首先一个线程成功获得一个条件变量后,调用此条件变量的wait()方法会导致这个线程释放这个锁,并进入“blocked”状态,直到另一个线程调用同一个条件变量的notify()方法来唤醒那个进入“blocked”状态的线程。如果调用这个条件变量的notifyAll()方法的话就会唤醒所有的在等待的线程。

    如果程序或者线程永远处于“blocked”状态的话,就会发生死锁。所以如果使用了锁、条件变量等同步机制的话,一定要注意仔细检查,防止死锁情况的发生。对于可能产生异常的临界区要使用异常处理机制中的finally子句来保证释放锁。等待一个条件变量的线程必须用notify()方法显式的唤醒,否则就永远沉默。保证每一个wait()方法调用都有一个相对应的notify()调用,当然也可以调用notifyAll()方法以防万一。








Python线程编程(三)同步队列



我们经常会采用生产者/消费者关系的两个线程来处理一个共享缓冲区的数据。例如一个生产者线程接受用户数据放入一个共享缓冲区里,等待一个消费者线程对数据取出处理。但是如果缓冲区的太小而生产者和消费者两个异步线程的速度不同时,容易出现一个线程等待另一个情况。为了尽可能的缩短共享资源并以相同速度工作的各线程的等待时间,我们可以使用一个“队列”来提供额外的缓冲区。

创建一个“队列”对象

import Queue
myqueue = Queue.Queue(maxsize = 10)

Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中

myqueue.put(10)

调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出

myqueue.get()

调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为1。如果队列为空且block为1,get()就使调用线程暂停,直至有项目可用。如果block为0,队列将引发Empty异常。

我们用一个例子来展示如何使用Queue

# queue_example.py
from Queue import Queue
import threading
import random
import time

# Producer thread
class Producer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),'adding',i,'to queue'
self.sharedata.put(i)
time.sleep(random.randrange(10)/10.0)
print self.getName(),'Finished'

# Consumer thread
class Consumer(threading.Thread):
def __init__(self, threadname, queue):
threading.Thread.__init__(self, name = threadname)
self.sharedata = queue
def run(self):
for i in range(20):
print self.getName(),'got a value:',self.sharedata.get()
time.sleep(random.randrange(10)/10.0)
print self.getName(),'Finished'

# Main thread
def main():
queue = Queue()
producer = Producer('Producer', queue)
consumer = Consumer('Consumer', queue)

print 'Starting threads ...'
producer.start()
consumer.start()

producer.join()
consumer.join()

print 'All threads have terminated.'

if __name__ == '__main__':
main()

示例代码中实现了两个类:生产者类Producer和消费者类Consumer。前者在一个随机的时间内放入一个值到队列queue中然后显示出来,后者在一定随机的时间内从队列queue中取出一个值并显示出来。




2006年5月30日

Doing better research and project



今天又是“源头活水来”的一天。

上午的英语课上,我完成了这学期的第二个英语报告,题目是“How to do research? In my view”。报告很成功,老师一贯的给每位同学提了一些建议。给我的建议有这么几条:(1)说“我完成了很多工作”时,不用“I have done so many works”,而要用“I have done so much work”;(2)发音中有一个单词需要纠正一下,那就是analyze,一定要分清它的名词形式analysis二者的发音;(3)演讲过程中注意去掉多余的"ok",否则显得不够正式。这是今年来,我第二次收到别人对于我的英语的建议。感觉这种建议非常的好,我可以好好的有针对性的提高一下。我的演讲内容包括了自己三年以来做研究的一些经验和心得。对于研究的感觉,我是“It is exciting with depressed, just like on wave.”老师说,这么多年来,我是第一个她看到的对研究exciting的人,从我的演讲之中确实发现我对研究充满激情。我的演讲也引来了几位同学的一些讨论,针对这些问题我们也进行了很好的讨论和交流。确实,在我的博士英语班同学面前谈如何作研究,确实有点班门弄斧。但是还好的是得到了一些对于研究感受的共鸣。我研究的最后观点是,(1)研究是一个充满着激动和沮丧的过程,需要不断的激动和不断的沮丧。只有经历了这种过程的不断迭代才能成为一位真正的Researcher;(2)研究是一个整体的过程,需要认真的去提高各个步骤的能力,掌握的技巧越多,越能高效的完成你的研究;(3)做项目,对于做研究是一种互补,把项目当成研究,把研究当成项目,可以实现鱼和熊掌兼得;(4)保持激情,保持阅读,多多思考,多多动手,最后你会像我一样热爱研究。

我的研究结束语是,“I love research! And you?”

晚上的时候,实验室的Coding Group请到了IBM俱乐部的负责人吴晋老师。吴老师在项目开发方面经验非常丰富。他指导的俱乐部多数成成员都是大二大三的学生,但是却在国内的很多重量级的编程大赛中取得了非常那个卓越的成绩。其中除了吴老师的个人魅力之外,就是他对项目管理的一整套的心得体会。

在短短的一个半小时中,我们聊到了很多很多的话题。我非常感兴趣的几个问题是:
(1)如何引导高校中参加项目的学生很好的完成项目?
(2)对于一些非常好的软件工程工具软件的使用情况如何?
(3)有没有一些特殊的小组合作方式?

讨论得到的结论是:
(1)需要针对具体的同学来具体设定每个学生在项目开发中的角色。比如通过小组成员轮流主持讨论会的方式来考察学生项目管理的爱好情况和能力,或者在介绍完每个项目之后通过查看每位同学对项目的看法来了解学生然后分配具体的角色。

(2)高校中采用大二大三指导学生来完成工作的合作模式需要一些和在公司中开发软件不同的方法。需要针对目前具体的问题来具体分析如何使用一些非常时髦的软件工程软件。现在能够使用到的软件主要包括:wiki, gf的项目管理网站,cvs的代码管理,单元测试测工具,bug管理的软件等等。各个软件的使用都需要一些时间来揣摩具体的小组合作模式。

(3)上面提到的wiki是一种非常好的合作模式。在其中可以非常方便的进行协同开发文档。比如小组例会定义好的任务列表,具体的项目开发文档,甚至完成的代码等,都可以在其中非常好的完成。而且可以形成非常恰当的经验积累,对于后来的成员和项目管理可以起到很好的实例教学的功能。

现在,我正在我负责的CR四人小组中扮演着一个项目管理者的角色。在吴老师的这次座谈之中体会到了很多。我会将一些比较好的模式推广到我们小组中的。非常巧的是,在座谈会之后,我们的四人CR小组召开了我们的每周例会。今天主要是忻舟和立正华介绍了两片xiaofeng yang的论文,其中我们学习了一些很好的技巧来改善我们目前的指代消解研究工作。之后的常规讨论我们各自简述了一周的工作和项目进展和下周的计划。

嗯,总体感觉,今天收获巨大。可以好好的指导我们下一步的研究和项目工作。

2006年5月29日

问渠哪得清如许

记得初中毕业的时候,我最尊敬的数学老师送给我一句话,“问渠哪得清如许,唯有源头活水来”。今天是执行俺的最新的个人事务管理计划的第一天。今天下来最大的感受是,阅读论文太重要了。现在得到的结论是,当你对研究没有激情的时候需要认真的阅读几篇相关的论文,那样你的那一坛水就能重新变得清澈起来。

今天,我主要阅读了三篇论文,两篇是NLPXML系列的,一篇是关于共指消解的。NLPXML的文章都是2001年首届会议的。其中提到的一些想法和做法都和我们现在实现的工作类似。这个问题也说明,我们现在的工做比国外落后了五年左右。还好,现在借助于这次阅读论文的机会可以很好的弥补这种差距。指代消解的论文是ICML2005的一篇指代消解的论文。其中提到的一个非常好的观点是,对于特定的机器学习任务,需要优化的有Sample selection, feature selection, feature representation, parameters choosing,和data validation。这整个的优化空间非常巨大,不能仅仅根据一些特定的设置来完成一些相关的机器学习任务。事实上,作者在UCI数据和共指消解的数据集上完成的工作都体现了这个结果。这个结论非常的有意义。以往我们关注于机器学习方法的选择的研究手段是不合理的,应该更多的关注到对问题的理解和建模。

这些新鲜的想法就像一记清水,注入我当前的研究和开发之中。现在对于研究,我的感觉又恢复到Exciting状态了。哈哈,实现逆转了。加油吧,我会更加努力的。Keep reading everyday. It will refresh you everyday!

2006年5月28日

NLPXML

找到这个系列,决定好好学习,然后结合我正在完成的项目添加一些新的理念在里面。

地址:NLPXML Workshop Series http://www.ling.helsinki.fi/~gwilcock/NLPXML/

NLPXML Workshop Series

1st NLP and XML Workshop (NLPXML-2001), Tokyo, 30 November 2001

2nd Workshop on NLP and XML (NLPXML-2002), Taipei, 1 September 2002
Language Technology and the Semantic Web:
3rd Workshop on NLP and XML (NLPXML-2003), Budapest, 13 April 2003

RDF/RDFS and OWL in Language Technology:
4th Workshop on NLP and XML (NLPXML-2004), Barcelona, 25 July 2004

Multi-dimensional Markup in Natural Language Processing:
5th Workshop on NLP and XML (NLPXML-2006), Trento, 4 April 2006


2006年5月27日

爱上“数学之美 系列”

近来,Google黑板报(http://googlechinablog.com/)推出了研究员吴军撰写的“数学之美 系列”。现在能够看到的文章有如下几篇:

数学之美 系列一 -- 统计语言模型
数学之美 系列二 -- 谈谈中文分词
数学之美 系列三 -- 隐含马尔可夫模型在语言处理中的应用
数学之美系列 4 -- 怎样度量信息?
数学之美系列五 -- 简单之美:布尔代数和搜索引擎的索引
数学之美系列六 -- 图论和网络爬虫 (Web Crawlers)
数学之美 系列七 -- 信息论在信息处理中的应用

昨天和今天,我把这几篇文章从头到尾认真的阅读了一遍。其中领会到了很多的东西。比如信息熵计算公式的来由,网络爬虫的原理和基本方法。现在我感觉自觉哦非常喜欢这个系列的文章,期待出现更多的相关文章。推荐大家也学习一下。

学习别人的东西,要抱着批判的眼光。在学习这几篇文章的时候我发现了一个问题。那就是“数学之美 系列一 -- 统计语言模型”在估计P(wi|wi-1)时的那段说明。帖子中的这段描述原文如下:


接下来的问题就是如何估计 P (wi|wi-1)。现在有了大量机读文本后,这个问题变得很简单,只要数一数这对词(wi-1,wi) 在统计的文本中出现了多少次,以及 wi-1 本身在同样的文本中前后相邻出现了多少次,然后用两个数一除就可以了,(P(wi|wi-1) = P (wi)/[P(wi-1,wi)]。


结合自己学习NLP时候的感觉,这段描述好像存在一些问题。我觉得应该修改为如下内容:


接下来的问题就是如何估计 P (wi|wi-1)。现在有了大量机读文本后,这个问题变得很简单,只要数一数这对词(wi-1,wi) 按照wi-1wi形式在统计的文本中出现了多少次,以及 wi-1 本身在同样的文本中出现了多少次,然后用两个数一除就可以了,(P(wi|wi-1) = C (wi-1wi)/C (wi-1),C表示计数)。


原文中的概率公式P(wi|wi-1) = P (wi)/[P(wi-1,wi)]好像也写错了。

呵呵,可能算是有点较真了,不过,扣除这点瑕疵,我还是非常喜欢Google的黑板报的。从这里了解到了很多Google中国的情况,但愿自己有一天能够到那里实习一下,也好体验一下那种Google的文化和自由。

2006年5月26日

摇滚.经典中国 哈尔滨啤酒两岸三地巨星演唱会

昨天晚上,有幸和亚杰一起参加了哈尔滨国际会展体育中心体育场举行的“摇滚.经典中国 哈尔滨啤酒两岸三地巨星演唱会”。这是俺这辈子第一次亲临演唱会,总算是感受到了那种演唱会上的歌迷的热情。演出明星有黄贯中、伍佰、许巍、黑豹、胡彦斌。

演唱会节目单

黄贯中:光辉岁月、海阔天空、愿我能、季节、我在存在
黑豹: 无地自容、错觉、别去糟蹋、DONT BREAK MY HEART、I DONT WANNA SAY GOOD-BYE
许巍: 曾经的你、蓝莲花、礼物、旅行
胡彦斌: 红颜、我的未来不是梦、WAITING FOR YOU、皇帝
伍佰: 挪威森林、白鸽、浪人情歌、泪桥

感觉非常不错!在演唱会现场看到了亚杰的一位高中同学。同学相见,总是那么的亲切和热情,我们聊到了很多。哈哈,这个同学管我叫“妹夫”。还真是有点不习惯呀^_^

演唱会上,各位歌星非常的卖力,我最喜欢的歌手是伍佰和胡彦斌,《白鸽》和《红颜》我觉得非常不错。演唱会现场的真是人山人海,体育场四面都坐满了人,简单算算应该有1万多人吧。现场的音效以及各种烟雾、灯光,让人非常的震撼。

嗯,感觉非常不错的!

2006年5月25日

个人时间管理实践-时间规划

当你有很多的工作需要完成的时候,我认为时间管理和分配就显得非常的重要了。记得本科时代,我经常作的一件事情就是将自己的时间片在草纸上整理出来,同时把需要完成的事情也列在纸上,然后开始逐一的匹配和协调,之后就是严格的执行。这种工作几乎每个月至少进行一次。进入研究生一以来,我经常感觉到自己需要完成的事情非常多,但是经常出现的情况是我的时间分配很混乱,经常不清楚究竟自己分配了多少时间在各个点上,久而久之就形成了恶性循环,自己也变得越来越忙了。呵呵,最近有类似感觉的朋友请注意反思一下自己最近的时间分配情况,可能你的不断忙碌就是由于时间没有好好分配的原因。

找到了自己近来一直忙碌的原因,这下可要好好的治理一下了。我拿出自己心爱的几个软件之一的MindManager,开始将自己本科时代的那种规划方法发挥了一下。哈哈,在这个软件上进行时间规划可真是非常方便呀。几下子下来,俺对于自己一周的时间和需要完成的任务真是一清二楚了。扣除周末的休息时间,俺现在觉得自己的时间和任务刚好能够匹配上的,而且各个方面都考虑到了。

看到自己安排完成的时间表,俺觉得自己一下子理清了自己以往混乱的状态。但是在各个任务上的细节好没有详细的安排出来。这个工作我需要在明天再花上一天时间来好好完成。磨刀不误砍柴功,俺深知这个道理。这次时间规划俺会非常认真的对待的。因为,太累的感觉很不好,需要好好的调整一下了。

朋友,如果你也很累,那也磨一下刀吧^_^