python多线程入门之旅一

所有代码来自python核心编程

参考python核心编程一书,学习多线程工作模式,多线程实现主要模块thread,threading,Queue等。

首先实现单线程一段代码:

from time import sleep,ctime

def loop0():
    print 'start loop 0 at:', ctime()
    sleep(4)
    print 'loop 0 done at:', ctime()

def loop1():
    print 'start loop 1 at:', ctime()
    sleep(2)
    print 'loop 1 done at:', ctime()

def main():
    print 'starting at:', ctime()
    loop0()
    loop1()
    print 'all done at:', ctime()

if __name__ == '__main__':
    main()

运行结果如下:能看到两个函数按顺序在6秒内完成

starting at: Thu Jun 08 16:42:00 2017
start loop 0 at: Thu Jun 08 16:42:00 2017
loop 0 done at: Thu Jun 08 16:42:04 2017
start loop 1 at: Thu Jun 08 16:42:04 2017
loop 1 done at: Thu Jun 08 16:42:06 2017
all done at: Thu Jun 08 16:42:06 2017

我们将首先利用thread修改代码,来看看效果

import thread
from time import sleep,ctime

def loop0():
    print 'start loop 0 at:', ctime()
    sleep(4)
    print 'loop 0 done at:', ctime()

def loop1():
    print 'start loop 1 at:', ctime()
    sleep(2)
    print 'loop 1 done at:', ctime()

def main():
    print 'starting at:', ctime()
    a=thread.start_new_thread(loop0,())
    b=thread.start_new_thread(loop1,())
    sleep(6)
    print 'all done at:', ctime()

if __name__ == '__main__':
    main()

运行结果如下:我们能看到loop0和loop1是并发执行的

starting at: Thu Jun 08 16:44:44 2017
start loop 1 at: Thu Jun 08 16:44:44 2017
start loop 0 at: Thu Jun 08 16:44:44 2017
loop 1 done at: Thu Jun 08 16:44:46 2017
loop 0 done at: Thu Jun 08 16:44:48 2017
all done at: Thu Jun 08 16:44:50 2017

我们添加了sleep(6)来实现子线程与主线程的同步,我们可以尝试把sleep(6)改为sleep(2)来看看效果:

starting at: Thu Jun 08 16:47:52 2017
start loop 0 at: Thu Jun 08 16:47:52 2017
start loop 1 at: Thu Jun 08 16:47:52 2017
all done at: Thu Jun 08 16:47:54 2017
Unhandled exception in thread started by 
sys.excepthook is missing
lost sys.stderr

直接导致子线程未结束时候主线程已经退出
下面的例子我们再利用thread的锁机制来保证线程的同步

import thread
from time import sleep,ctime

loops = [4,2]

def loop(nloop,nsec,lock):
    print 'start loop',nloop, 'at:', ctime()
    sleep(nsec)
    print 'loop', nloop, 'done at:', ctime()
    lock.release()

def main():
    print 'starting at:', ctime()
    locks = []
    nloops = range(len(loops))

    for i in nloops:
        lock = thread.allocate_lock()
        lock.acquire()
        locks.append(lock)

    for i in nloops:
        thread.start_new_thread(loop,(i,loops[i],locks[i]))
        #sleep(1)

    for i in nloops:
        while locks[i].locked():
            pass

    print 'all done at:', ctime()

if __name__ == '__main__':
    main()

运行效果:

starting at: Thu Jun 08 17:05:59 2017
start loop 0 at: Thu Jun 08 17:05:59 2017start loop
 1 at: Thu Jun 08 17:05:59 2017
loop 1 done at: Thu Jun 08 17:06:01 2017
loop 0 done at: Thu Jun 08 17:06:03 2017
all done at: Thu Jun 08 17:06:03 2017

我们能看到两个线程是并发的,并且主线程等子线程结束后才结束的。但是与书上不一致的是启动线程中的输出是乱掉了,不清楚为何和书上为何不一致。
根据作者的解释建议我们尽量不要使用thread模块,而使用更高级的threading模块。
下面我们先看下threading模块的对象:

threading模块对象

利用threading模块的thread类有三种方法:

• 创建Thread 的实例,传给它一个函数。
• 创建Thread 的实例,传给它一个可调用的类实例。
• 派生Thread 的子类,并创建子类的实例。

首先利用第一种方法来修改上面的例子:

• 创建Thread 的实例,传给它一个函数。

import threading
from time import sleep, ctime

loops = [4, 2]


def loop(nloop, nsec):
    print 'start loop', nloop, 'at:', ctime()
    sleep(nsec)
    print 'loop', nloop, 'at:', ctime()


def main():
    print 'starting at:', ctime()
    threads = []
    nloops = range(len(loops))
    for i in nloops:
        t = threading.Thread(target=loop, args=(i, loops[i]))
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print 'all done at:', ctime()


if __name__ == '__main__':
    main()

运行效果:跟之前的代码对比,我们用一组thread对象代替了之前实现的锁,并且代码不会立即执行,只在你希望它执行的时候执行;另外使用join()方法比等待锁释放的无限循环更清晰。

starting at: Fri Jun 09 10:21:45 2017
start loop 0 at: Fri Jun 09 10:21:45 2017
start loop 1 at: Fri Jun 09 10:21:45 2017
loop 1 at: Fri Jun 09 10:21:47 2017
loop 0 at: Fri Jun 09 10:21:49 2017
all done at: Fri Jun 09 10:21:49 2017

接着我们使用第二种方法来修改代码

• 创建Thread 的实例,传给它一个可调用的类实例。

import threading
from time import sleep, ctime

loops = [4, 2]


class ThreadFunc(object):
    def __init__(self, func, args, name=''):
        self.name = name
        self.func = func
        self.args = args

    def __call__(self):
        self.func(*self.args)

def loop(nloop, nsec):
    print 'start loop', nloop, 'at:', ctime()
    sleep(nsec)
    print 'loop', nloop, 'at:', ctime()

def main():
    print 'starting at:', ctime()
    threads = []
    nloops = range(len(loops))
    for i in nloops:
        t = threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__))
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print 'all done at:', ctime()


if __name__ == '__main__':
    main()

运行结果:在上个代码中添加一个新类ThreadFunc即得到这个代码,比起一个函数有更好的灵活性,而不仅仅是单个函数

starting at: Fri Jun 09 10:31:34 2017
start loop 0 at: Fri Jun 09 10:31:34 2017
start loop 1 at: Fri Jun 09 10:31:34 2017
loop 1 at: Fri Jun 09 10:31:36 2017
loop 0 at: Fri Jun 09 10:31:38 2017
all done at: Fri Jun 09 10:31:38 2017

我们再看看第三种方式的代码:

• 派生Thread 的子类,并创建子类的实例。

import threading
from time import sleep, ctime

loops = [4, 2]


class MyThread(threading.Thread):
    def __init__(self, func, args, name=''):
        threading.Thread.__init__(self)
        self.name = name
        self.func = func
        self.args = args

    def run(self):
        self.func(*self.args)

def loop(nloop, nsec):
    print 'start loop', nloop, 'at:', ctime()
    sleep(nsec)
    print 'loop', nloop, 'at:', ctime()

def main():
    print 'starting at:', ctime()
    threads = []
    nloops = range(len(loops))
    for i in nloops:
        t = MyThread(loop,(i,loops[i]),loop.__name__)
        threads.append(t)
    for i in nloops:
        threads[i].start()
    for i in nloops:
        threads[i].join()
    print 'all done at:', ctime()


if __name__ == '__main__':
    main()

本例直接对Thread子类化,使我们具有更多的灵活性。
使用这种模式必须首先先调用基类的构造函数,即

基类构造函数调用

还有之前的call()特殊方法必须写为run()。
下面是运行结果:

starting at: Fri Jun 09 12:10:54 2017
start loop 0 at: Fri Jun 09 12:10:54 2017
start loop 1 at: Fri Jun 09 12:10:54 2017
loop 1 at: Fri Jun 09 12:10:56 2017
loop 0 at: Fri Jun 09 12:10:58 2017
all done at: Fri Jun 09 12:10:58 2017

如果我们要获取子线程返回的结果怎么处理呢,下篇文章我们在讨论。

推荐阅读更多精彩内容

  • 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,...
    了不起的顾斯比阅读 891评论 2 17
  • 线程 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0....
    不浪漫的浪漫_ea03阅读 52评论 0 0
  • 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0.1秒,...
    chen_000阅读 72评论 0 0
  • 来源:数据分析网Threading 模块从 Python 1.5.2 版开始出现,用于增强底层的多线程模块 thr...
    PyChina阅读 1,022评论 1 5
  • 上一篇文章讲了python多线程的基础知识和thread模块,这一篇着重讲解一下threading模块 threa...
    戏说江湖阅读 493评论 0 1