python多线程创建与使用(转)
原文:
创建多线程
创建多线程主要有2种方式。
- 使用threading.thread函数
- 继承threading类
1. 使用threading.thread函数
import threading def tom(number): print threading.currentthread().getname() print number if __name__ == "__main__": number = ["zero", "one", "two", "three", "four"] sex = ["man", "woman"] for i in range(5): th = threading.thread(target=tom, args=(number[i],)) # th.setname('mythread') # print th.getname() th.start()
说明:thread()函数有2个参数,一个是target,内容为子线程要执行的函数名称;另一个是args,内容为需要传递的参数。args 参数看起来有些奇怪,那是因为我们需要传递一个序列给tom函数,但它只接受一个变量,所以我们把逗号放在尾部来创建只有一个参数的序列。创建完子线程,将会返回一个对象,调用对象的start方法,可以启动子线程。
当你运行以上这段代码,会得到以下输出:
thread-1 zero thread-2 one thread-3 two thread-4 three thread-5 four
线程对象的方法:
start() 开始线程的执行
run() 定义线程的功能的函数
join(timeout=none) 程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout秒
getname() 返回线程的名字
setname() 设置线程的名字
isalive() 布尔标志,表示这个线程是否还在运行
isdaemon() 返回线程的daemon标志
setdaemon(daemonic) 把线程的daemon标志设为daemonic(一定要在start()函数前调用)
t.setdaemon(true) 把父线程设置为守护线程,当父进程结束时,子进程也结束
2. 继承threading类
import threading class mythread(threading.thread): def __init__(self,number): threading.thread.__init__(self) self.number = number def run(self): print threading.current_thread().getname() print self.number if __name__ == "__main__": for i in range(5): th = mythread(i) th.start()
当你运行以上这段代码,会得到以下输出:
thread-1 0 thread-2 1 thread-3 2 thread-4 3 thread-5 4
当然,通常情况下你不会希望输出打印到标准输出。如果不幸真的这么做了,那么最终的显示效果将会非常混乱。你应该使用 python 的 logging 模块。它是线程安全的,并且表现出色。让我们用 logging 模块修改上面的例子并且给我们的线程命名。代码如下:
import threading import logging def get_logger(): #创建一个被设置为调试级别的日志记录器 logger = logging.getlogger("mylogger") logger.setlevel(logging.debug) #设置每行日志的格式。格式包括时间戳、线程名、日志记录级别以及日志信息 fh = logging.filehandler("threading.log") fmt = '%(asctime)s - %(threadname)s - %(levelname)s - %(message)s' formatter = logging.formatter(fmt) fh.setformatter(formatter) logger.addhandler(fh) return logger def tom(number, logger): logger.debug(number) if __name__ == "__main__": logger = get_logger() number = ["zero", "one", "two", "three", "four"] sex = ["man", "woman"] for i in range(5): th = threading.thread(target=tom, args=(number[i],logger)) # th.setname('mythread') # print th.getname() th.start()
通过继承的方法:
import threading import logging class mythread(threading.thread): def __init__(self,number,logger): threading.thread.__init__(self) self.number = number self.logger = logger def run(self): self.logger.debug("calling-thread") tom(self.number, self.logger) def get_logger(): logger = logging.getlogger("mylogger") logger.setlevel(logging.debug) fh = logging.filehandler("threading.log") fmt = '%(asctime)s - %(threadname)s - %(levelname)s - %(message)s' formatter = logging.formatter(fmt) fh.setformatter(formatter) logger.addhandler(fh) return logger def tom(number, logger): if __name__ == "__main__": logger = get_logger() for i in range(5): th = mythread(i, logger) th.start()
在 tom 函数中,我们把 print 语句换成 logging 语句。你会注发现,在创建线程时,我们给 doubler 函数传入了 logger 对象。这样做的原因是,如果在每个线程中实例化 logging 对象,那么将会产生多个 logging 单例(singleton),并且日志中将会有很多重复的内容
线程锁与线程同步
由于物理上得限制,各cpu厂商在核心频率上的比赛已经被多核所取代。为了更有效的利用多核处理器的性能,就出现了多线程的编程方式,而随之带来的就是线程间数据一致性和状态同步的困难。解决多线程之间数据完整性和状态同步的最简单方法自然就是加锁。锁由 python 的 threading 模块提供,并且它最多被一个线程所持有。当一个线程试图获取一个已经锁在资源上的锁时,该线程通常会暂停运行,直到这个锁被释放。
有两种方式为线程加锁:
try...finally
with
代码如下:
import threading import logging lock = threading.lock() class mythread(threading.thread): def __init__(self,number,logger): threading.thread.__init__(self) self.number = number self.logger = logger def run(self): lock.acquire() try: self.logger.debug("calling-thread") tom(self.number, self.logger) finally: lock.release() def get_logger(): logger = logging.getlogger("mylogger") logger.setlevel(logging.debug) fh = logging.filehandler("threading.log") fmt = '%(asctime)s - %(threadname)s - %(levelname)s - %(message)s' formatter = logging.formatter(fmt) fh.setformatter(formatter) logger.addhandler(fh) return logger def tom(number, logger): with lock: logger.debug(number) if __name__ == "__main__": logger = get_logger() for i in range(5): with lock: th = mythread(i, logger) th.start()
当你真正运行这段代码时,你会发现它只是挂起了。究其原因,是因为我们只告诉 threading 模块获取锁。所以当我们调用第一个函数时,它发现锁已经被获取,随后便把自己挂起了,直到锁被释放,然而这将永远不会发生。
真正的解决办法是使用重入锁(re-entrant lock)。threading 模块提供的解决办法是使用 rlock 函数。即把 lock = threading.lock() 替换为 lock = threading.rlock(),然后重新运行代码,现在代码就可以正常运行了。
线程通信
某些情况下,你会希望线程之间互相通信。就像先前提到的,你可以通过创建 event 对象达到这个目的。但更常用的方法是使用队列(queue)。在我们的例子中,这两种方式都会有所涉及。下面让我们看看到底是什么样子的:
import threading import queue def creator(data, q): """ 生成用于消费的数据,等待消费者完成处理 """ print('creating data and putting it on the queue') for item in data: evt = threading.event() q.put((item, evt)) print('waiting for data to be doubled') evt.wait() def my_consumer(q): """ 消费部分数据,并做处理 这里所做的只是将输入翻一倍 """ while true: data, evt = q.get() print('data found to be processed: {}'.format(data)) processed = data * 2 print(processed) evt.set() q.task_done() if __name__ == '__main__': q = queue() data = [5, 10, 13, -1] thread_one = threading.thread(target=creator, args=(data, q)) thread_two = threading.thread(target=my_consumer, args=(q,)) thread_one.start() thread_two.start() q.join()
让我们掰开揉碎分析一下。首先,我们有一个创建者(creator)函数(亦称作生产者(producer)),我们用它来创建想要操作(或者消费)的数据。然后用另外一个函数 my_consumer 来处理刚才创建出来的数据。creator 函数使用 queue 的 put 方法向队列中插入数据,消费者将会持续不断的检测有没有更多的数据,当发现有数据时就会处理数据。queue 对象处理所有的获取锁和释放锁的过程,这些不用我们太关心。
在这个例子中,先创建一个列表,然后创建两个线程,一个用作生产者,一个作为消费者。你会发现,我们给两个线程都传递了 queue 对象,这两个线程隐藏了关于锁处理的细节。队列实现了数据从第一个线程到第二个线程的传递。当第一个线程把数据放入队列时,同时也传递一个 event 事件,紧接着挂起自己,等待该事件结束。在消费者侧,也就是第二个线程,则做数据处理工作。当完成数据处理后就会调用 event 事件的 set 方法,通知第一个线程已经把数据处理完毕了,可以继续生产了。
最后一行代码调用了 queue 对象的 join 方法,它会告知 queue 等待所有线程结束。当第一个线程把所有数据都放到队列中,它也就运行结束了。
推荐阅读
-
python day19 : 购物商城作业,进程与多线程
-
使用TortoiseGit操作分支的创建与合并
-
python数据库操作常用功能使用详解(创建表/插入数据/获取数据)
-
Python机器学习库scikit-learn安装与基本使用教程
-
对sklearn的使用之数据集的拆分与训练详解(python3.6)
-
Windows中安装使用Virtualenv来创建独立Python环境
-
Python for循环与range函数的使用详解
-
Python XML转Json之XML2Dict的使用方法
-
Python wxPython库使用wx.ListBox创建列表框示例
-
maya交互式创建怎么取消与使用?