您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 信息化管理 > Python标准模块--threading
Python标准模块--threading1模块简介threading模块在Python1.5.2中首次引入,是低级thread模块的一个增强版。threading模块让线程使用起来更加容易,允许程序同一时间运行多个操作。不过请注意,Python中的线程最好是与IO操作一起工作,比如从网络上下载资源或者从你的电脑中读取文件和目录。如果你需要处理一些CPU密集的任务,你最好是看看Python的multiprocessing模块。原因就是Python有GIL锁(解释器全局锁),使得所有的线程在主线程内运行。由于这个原因,当你使用线程执行CPU密集型任务时,你可能会发现它会运行的很慢。下面,我们主要集中在IO操作--线程做的好的场景。2模块使用2.1线程入门一个线程允许你像运行一个独立的程序一样,运行一段独立的代码,类似于subprocess,区别在于,线程运行的是函数或者类,而不是一个独立的程序。我发现使用一个具体的实例会有助于我们更加理解概念。实例如下,importthreadingdefdoubler(number):print(threading.currentThread().getName()+\n)print(number*2)printif__name__==__main__:foriinrange(5):my_thread=threading.Thread(target=doubler,args=(i,))my_thread.start()my_thread.join()我们首先引入threading模块,创建一个常规的函数doubler。这个函数将输入值乘以2,它也打印出调用这个函数的线程的名字,最后打印一个空白行。你也许会注意到当我们实例化一个线程时,我们设置它的target为我们的doubler函数,然后我们将变量传递给这个函数。使用args参数看起来有些奇怪,是因为我们需要向doubler函数传递一个序列,但是它只介绍一个变量,我们我们需要在末尾放入一个逗号,从而构造出一个序列。如果你想等待一个线程结束,你需要调用它的join()方法。当你运行这段代码时,你应该可以得到如下的结果,Thread-10Thread-22Thread-34Thread-46Thread-58一般情况下,你并不希望将输出打印到标准输出上,当你这样做的时候,会导致乱七八糟的混乱。你应该使用Python的logging模块。这篇文章具体会告诉你如何使用logging模块,Python标准模块--logging。logging模块是线程安全的,性能也很优越。让我们把上面的代码修改一下,加上logging模块,如下所示,importthreadingimportloggingdefget_logger():logger=logging.getLogger(threading_example)logger.setLevel(logging.DEBUG)fh=logging.FileHandler(threading.log)fmt=%(asctime)s-%(threadName)s-%(levelname)s-%(message)sformatter=logging.Formatter(fmt)fh.setFormatter(formatter)logger.addHandler(fh)returnloggerdefdoubler(number,logger):logger.debug('doublefunctionexecuting')result=number*2logger.debug(doublefunctionendedwith{}.format(result))if__name__==__main__:logger=get_logger()thread_names=[Mike,George,Wanda,Dingbat,Nina]foriinrange(5):my_thread=threading.Thread(target=doubler,name=thread_names[i],args=(i,logger))my_thread.start()这段代码最大的改动就是加入了get_logger函数。这段代码创建一个级别为debug的logger。它将会把日志保存在当前的工作目录(例如,这个脚本所运行的目录),日志文件名为threading.log,然后我们设置每行日志的格式。这个日志格式包括时间戳、线程名字、日志等级和要打印的消息。在doubler函数中,我们将print语句修改为logging语句。你将会注意到当我们创建线程时,我们将logger传入到doubler函数中。我们这么做的原因是当你在每个线程中实例化logging对象时,你将会得到多个日志记录单例,你的日志中将会有很多重复的行。最后,我们通过创建一个名称列表用于给创建的线程进行命名,通过使用name这个参数,给每个线程设置一个指定的名称。当你运行这段代码时,你就会在日志文件中得到如下内容,2016-11-1114:34:35,350-Mike-DEBUG-doublefunctionexecuting2016-11-1114:34:35,350-Mike-DEBUG-doublefunctionendedwith02016-11-1114:34:35,350-George-DEBUG-doublefunctionexecuting2016-11-1114:34:35,350-Wanda-DEBUG-doublefunctionexecuting2016-11-1114:34:35,351-George-DEBUG-doublefunctionendedwith22016-11-1114:34:35,351-Wanda-DEBUG-doublefunctionendedwith42016-11-1114:34:35,351-Dingbat-DEBUG-doublefunctionexecuting2016-11-1114:34:35,351-Dingbat-DEBUG-doublefunctionendedwith62016-11-1114:34:35,351-Nina-DEBUG-doublefunctionexecuting2016-11-1114:34:35,351-Nina-DEBUG-doublefunctionendedwith8输出具有很好的可解释性。对于这部分,我想挖掘出更多的主题也就是threading.Thread。让我们来看最后一个例子,不再直接调用Thread,我们创建我们的子类。importthreadingimportloggingclassMyThread(threading.Thread):def__init__(self,number,logger):threading.Thread.__init__(self)self.number=numberself.logger=loggerdefrun(self):logger.debug(Callingdoubler)doubler(self.number,self.logger)defget_logger():logger=logging.getLogger(threading_example)logger.setLevel(logging.DEBUG)fh=logging.FileHandler(threading_class.log)fmt=%(asctime)s-%(threadName)s-%(levelname)s-%(message)sformatter=(fmt)fh.setFormatter(formatter)logger.addHandler(fh)returnloggerdefdoubler(number,logger):logger.debug('doublefunctionexecuting')result=number*2logger.debug(doublefunctionendedwith{}.format(result))if__name__==__main__:logger=get_logger()thread_names=[Mike,George,Wanda,Dingbat,Nina]foriinrange(5):thread=MyThread(i,logger)thread.setName(thread_names[i])thread.start()在这个例子中,我们通过threading.Thread创建子类。我们传入想翻倍的数字number和logging对象。但是这次,我们通过另一种方式--调用线程对象的setName方法来设置线程名字。当你调用start,它将会通过调用run方法来运行你所定义的线程。在我们的类例,我们调用doubler函数来完成我们所需的处理。输出和之前的输出很相似,只是我们多加了一行调用日志。尝试着运行这段代码,看看你得到的是什么?2.2线程锁和同步当你创建了多个线程时,你可能会发现你需要考虑如何避免冲突。我的意思就是你可能会遇到多个线程在同一时间需要访问同一个资源。如果你不考虑这个问题,你将会遇到一些发生在最坏的时刻并且通常是在生产环境下的问题。解决方案就是使用线程锁。Python的threading模块提供了线程锁,线程锁被一个线程或者没有线程所拥有。一个线程尝试着获取资源上已经被锁的线程锁,那个线程会被中止,直到线程锁被释放。让我们来看看一个没有采用任何锁机制的实例,importthreadingtotal=0defupdate_total(amount):globaltotaltotal+=amountprint(total)if__name__==__main__:foriinrange(10):my_thread=threading.Thread(target=update_total,args=(5,))my_thread.start()可以调用time.sleep函数让这段程序更加有意思。这个问题主要还是一个线程调用update_total,在它完成更新之前,另一个线程也可能会去调用它并尝试着去更新。依赖于操作的顺序,这个值可能立刻就会被相加。让我们在这个函数中增加一个线程锁,有两种方式去实现这个,一种就是使用try/finally,正如我们所希望,线程锁常常出于释放状态。下面就是例子,importthreadingtotal=0lock=threading.Lock()defupdate_total(amount):globaltotallock.acquire()try:total+=amountfinally:lock.release()print(total=+str(total)+'\n')if__name__==__main__:foriinrange(10):my_thread=threading.Thread(target=update_total,args=(5,))my_thread.start()这里,我们在我们处理任何任务之前,先获取线程锁。然后,我们尝试着更新total的值,释放线程锁并打印出total当前的值。我们还可以使用Python的with语句来做类似的事情。importthreadingtotal=0lock=threading.Lock()defupdate_total(amount):globaltotalwithlock:total+=amountprint(total=+str(total)+'\n')if__name__==__main__:foriinrange(10):my_thread=threading.Thread(target=update_total,args=
本文标题:Python标准模块--threading
链接地址:https://www.777doc.com/doc-4210875 .html