18.1 引言/动机
多线程编程对于某些任务来说,是最理想的。这些任务具有以下特点:它们本质就是异步的,需要有多个并发事务,各个事务的运行顺序可以是不确定的,随机的,不可预测的。这样的编程任务可以被分成多个执行流,每个流都有一个要完成的目标。根据应用的不同,这些子任务可能都要计算出一个中间结果,用于合并得到最后的结果。
一个顺序执行的程序要从每个I/O(输入/输出)终端信道检查用户的输入时,程序无论如何也不能在读取I/O终端信道的时候阻塞。因为用户输入的到达时不确定的,则阻塞会导致其它I/O信息的数据不能被处理。顺序执行的程序必须使用非阻塞I/O,或是带有计时器的阻塞I/O(这样才能保证阻塞只是暂时的)。
由于顺序执行的程序只有一个线程执行。它要保证它要做的多任务,不会有某个任务占用太多的时间,而且要合理的分配用户的响应时间。执行多任务的顺序执行的程序一般程序控制流程都很复杂,难以理解。
使用多线程编程和一个共享的数据结构如Queue,这种程序任务可以用几个功能单一的线程来组织:
UserRequesThread:负责读取客户的输入,可能是一个I/O信道。程序可能创建多个线程,每个客户一个,请求会被放入队列中。
RequestProcessor:一个负责从队列中获取并处理请求的线程,它为下面那种线程提供输出
ReplyThread:负责把给用户的输出取出来,如果是网络应用程序就把结果发送出去,否则就保存到本地文件系统或数据库中。
18.2 线程和进程
18.2.1 什么是进程?
18.2.1 什么是进程?
计算机程序只不过是磁盘中可执行的,二进制的数据。它们只有在被读取到内存中,被操作系统调用的时候才开始它们的生命周期。进程(重量级进程)是程序的一次执行,每个进程都有自己的地址空间,内存,数据栈以及其它记录其运行轨迹的辅助数据。操作系统管理在其上运行的所有进程,并为这些进程公平的分配时间。进程也可以通过fork和spawn操作来完成其它的任务。不过各个进程有自己的内存空间,数据栈等,所以只能使用进程间通讯(IPC),而不能直接共享信息。
18.2.2 什么是线程
线程(轻量级进程)跟进程有些相似,不同的是:所有的线程运行在同一个进程中,共享相同的运行环境。它们可以想象成是在主进程或“主线程”中并行运行的“迷你进程”。
线程有开始,顺序执行和结束三部分。它有一个自己的指令指针,记录自己运行到什么地方。线程的运行可能被抢占(中断),或暂时的被挂起(也叫睡眠),让其它的线程运行,这叫做让步。一个进程中的各个线程之间共享同一片数据空间,所以线程之间可以比进程之间更方便的共享数据以及相互通讯。线程一般都是并发执行的,正式由于这种并行和数据共享的机制使得多个任务的合作变成可能。实际上,在单CPU的系统中,真正的并发是不可能的,每个线程会被安排成每次只运行一会,然后就把CPU让出来,让其它的线程去运行。在进程的整个运行过程中,每个线程都只做自己的事,在需要的时候跟其它的线程共享运行的结果。
当然,这样的共享并不是完全没有危险的。如果多个线程共同访问同一片数据,则由于数据访问的顺序不一样,有可能导致数据结果的不一致问题。
另一个需要注意的地方是:由于有的函数会在完成之前阻塞住,在没有特别为多线程做修改的情况下,这种“贪婪”的函数会让CPU的市价分配有所倾斜。导致各个线程分配到的运行时间可能不尽相同,不尽公平。
18.3 python,线程和全局解释器锁
对python虚拟机的访问由全局解释器锁(GIL)来控制,正是这个锁能保证同一时刻只有一个线程在运行。在多线程环境中,python虚拟机按以下方式执行:
1. 设置GIL
2. 切换到一个线程去运行
3. 运行:
a. 指定数量的字节码指令,或者
b. 线程主动让出控制(可以调用time.sleep(0))
4. 把线程设置为睡眠状态
5. 解锁GIL
6. 再次重复以上所有步骤
18.3.2 退出线程
当一个线程结束计算,它就退出了。线程可以调用thread.exit()之类的退出函数,也可以使用python退出进程的标准方法,如sys.exit()或抛出一个SystemExit异常等。不过,你不可以直接“杀掉”(“kill”)一个线程。
主线程应该是一个好的管理者,它要了解每个线程都要做些什么事,线程都需要什么数据和什么参数,以及在线程结束的时候,它们都提供了什么结果。这样,主线程就可以把各个线程的结果组合成一个有意义的最后结果。
18.3.4 没有线程支持的情况
01 | from time import sleep, ctime |
02 | def loop0(): |
03 | print "start loop 0 at:" , ctime() |
04 | sleep( 4 ) |
05 | print "loop 0 done at:" , ctime() |
06 | def loop1(): |
07 | print "start loop 1 at:" , ctime() |
08 | sleep( 2 ) |
09 | print "loop 1 done at:" , ctime() |
10 | def main(): |
11 | print "starting at:" , ctime() |
12 | loop0() |
13 | loop1() |
14 | print "all DONE at:" , ctime() |
15 |
16 | if __name__ = = "__main__" : |
17 | main() |
1 | >>> |
2 | starting at: Sat Jun 22 22 : 54 : 25 2013 |
3 | start loop 0 at: Sat Jun 22 22 : 54 : 25 2013 |
4 | loop 0 done at: Sat Jun 22 22 : 54 : 29 2013 |
5 | start loop 1 at: Sat Jun 22 22 : 54 : 29 2013 |
6 | loop 1 done at: Sat Jun 22 22 : 54 : 31 2013 |
7 | all DONE at: Sat Jun 22 22 : 54 : 31 2013 |
Python 提供了几个用于多线程编程的模块,包括thread, threading 和Queue 等。thread 和threading 模块允许程序员创建和管理线程。thread 模块提供了基本的线程和锁的支持,而threading提供了更高级别,功能更强的线程管理的功能。Queue 模块允许用户创建一个可以用于多个线程之间共享数据的队列数据结构。
18.4 thread模块
thread 模块函数
start_new_thread(function,
args, kwargs=None) 产生一个新的线程,在新线程中用指定的参数和可选的
kwargs来调用这个函数。
allocate_lock() 分配一个LockType 类型的锁对象
exit() 让线程退出
LockType类型锁对象方法
acquire(wait=None) 尝试获取锁对象
locked() 如果获取了锁对象返回True,否则返回False
release() 释放锁
我们来看一个简单的程序:
#/usr/bin/env python
#encoding:utf-8
'''使用线程'''
import thread
from time import ctime,sleep
def loop0():
print 'start loop0 at :' ,ctime()
sleep(4)
print 'loop0 done! at :' , ctime()
print ' '
def loop1():
print 'start loop1 at :',ctime()
sleep(2)
print 'loop1 done! at :',ctime()
def main():
print 'FUNCTION start at :', ctime()
thread.start_new_thread(loop0,()) #注意这里使用的是函数名,而不是loop0()
thread.start_new_thread(loop1,())
sleep(6)
print 'all DONE at :',ctime()
if __name__ == '__main__':
main()
程序输出有点乱:
FUNCTION start at : Sat Jun 14 13:55:13 2014
start loop0 at :start loop1 at : Sat Jun 14 13:55:13 2014
Sat Jun 14 13:55:13 2014
loop1 done! at : Sat Jun 14 13:55:15 2014
loop0 done! at : Sat Jun 14 13:55:17 2014
all DONE at : Sat Jun 14 13:55:19 2014
[Finished in 6.0s]
代码中sleep(6)是为了给两个线程提供运行时间,那有没有什么方法来代替sleep(6)呢?
使用线程和锁
#/usr/bin/env python
#encoding:utf-8
'''使用线程和锁:当子线程都执行完之后,立即结束主线程'''
import thread
from time import ctime,sleep
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 'FUNCTION start at :', ctime()
locks = []
nloops = range(len(loops))
for i in nloops:
lock = thread.allocate_lock()
lock.acquire()
locks.append(lock)#元组中的append方法,添加对象到元组中
for i in nloops:
thread.start_new_thread(loop,(i,loops[i],locks[i]))
#thread.start_new_thread(function, args)
for i in nloops:
while locks[i].locked():
pass
print 'all DONE at :',ctime()
if __name__ == '__main__':
main()
FUNCTION start at : Sat Jun 14 13:58:45 2014
start loop start loop 0 at : 1Sat Jun 14 13:58:45 2014
at : Sat Jun 14 13:58:45 2014
loop 1 done! at : Sat Jun 14 13:58:47 2014
loop 0 done! at : Sat Jun 14 13:58:49 2014
all DONE at : Sat Jun 14 13:58:49 2014
[Finished in 4.1s]
18.5 threading模块
threading 模块对象 描述
Thread 表示一个线程的执行的对象
Lock 锁原语对象(跟thread 模块里的锁对象相同)
RLock 可重入锁对象。使单线程可以再次获得已经获得了的锁(递归锁定)。
Condition 条件变量对象能让一个线程停下来,等待其它线程满足了某个“条件”。
如,状态的改变或值的改变。
Event 通用的条件变量。多个线程可以等待某个事件的发生,在事件发生后,
所有的线程都会被激活。
Semaphore 为等待锁的线程提供一个类似“等候室”的结构
BoundedSemaphore 与Semaphore 类似,只是它不允许超过初始值
Timer 与Thread 相似,只是,它要等待一段时间后才开始运行。
核心提示:守护线程
另一个避免使用thread 模块的原因是,它不支持守护线程。当主线程退出时,所有的子线程不论它们是否还在工作,都会被强行退出。有时,我们并不期望这种行为,这时,就引入了守护线程的概念
threading 模块支持守护线程,它们是这样工作的:守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求,它就在那等着。如果你设定一个线程为守护线程,就表示你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出。就像你在第16 章网络编程看到的,服务器线程运行在一个无限循环中,一般不会退出。
如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的daemon 属性。即,在线程开始(调用thread.start())之前,调用setDaemon()函数设定线程的daemon 标志(thread.setDaemon(True))就表示这个线程“不重要”
如果你想要等待子线程完成再退出, 那就什么都不用做, 或者显式地调用thread.setDaemon(False)以保证其daemon 标志为False。你可以调用thread.isDaemon()函数来判断其daemon 标志的值。新的子线程会继承其父线程的daemon 标志。整个Python 会在所有的非守护线程退出后才会结束,即进程中没有非守护线程存在的时候才结束。
18.5.1 Thread类
三种方式调用Thread类:
1. 创建一个Thread的实例,传给它一个函数
2. 创建一个Thread的实例,传给它一个可调用的类对象
3. 从Thread派生出一个子类,创建一个这个子类的实例
函数 描述
start() 开始线程的执行
run() 定义线程的功能的函数(一般会被子类重写)
join(timeout=None) 程序挂起,直到线程结束;如果给了timeout,则最多阻塞timeout 秒
getName() 返回线程的名字
setName(name) 设置线程的名字
isAlive() 布尔标志,表示这个线程是否还在运行中
isDaemon() 返回线程的daemon 标志
setDaemon(daemonic) 把线程的daemon 标志设为daemonic(一定要在调用start()函数前调用)
创建一个Thread的实例,传给它一个函数:
#/usr/bin/env python
#encoding:utf-8
'''使用threading:创建一个Thread的实例,传给它一个函数'''
import threading
from time import ctime,sleep
loops = [4,2,3]
def loop(nloop,nsec):
print 'start loop',nloop,'at :',ctime()
sleep(nsec)
print 'loop',nloop,'done! at :',ctime()
def main():
print 'MAIN thread start at :',ctime()
threads = []
nloops = range(len(loops))
for i in nloops:#create a instance for Thread class
t = threading.Thread(target=loop,args=(i,loops[i]))
threads.append(t)
for i in nloops:#start threads
threads[i].start()
for i in nloops:#wait for all threads finish
threads[i].join()
print 'all threads Done! at :',ctime()
if __name__ == '__main__':
main()
MAIN thread start at : Sat Jun 14 13:59:40 2014
start loop 0 at : Sat Jun 14 13:59:40 2014
start loop 1 at : Sat Jun 14 13:59:40 2014
start loop 2 at : Sat Jun 14 13:59:40 2014
loop 1 done! at : Sat Jun 14 13:59:42 2014
loop 2 done! at : Sat Jun 14 13:59:43 2014
loop 0 done! at : Sat Jun 14 13:59:44 2014
all threads Done! at : Sat Jun 14 13:59:44 2014
[Finished in 4.1s]
创建一个Thread的实例,传给它一个可调用的类对象
#/usr/bin/env python
#encoding:utf-8
'''使用threading:创建一个Thread的实例,传给它一个可调用的类对象'''
import threading
from time import ctime,sleep
loops = [4,2,3]
class threadFunc(object):
"""docstring for threadFunc"""
def __init__(self, func,args,name=''):
self.args = args
self.name = name
self.func = func
def __call__(self):#__call__表示可调用的实例
apply(self.func,self.args)
def loop(nloop,nsec):
print 'start loop',nloop,'at :',ctime()
sleep(nsec)
print 'loop',nloop,'done! at :',ctime()
def main():
print 'MAIN thread start at :',ctime()
threads = []
nloops = range(len(loops))
for i in nloops:#create a instance for Thread class
t = threading.Thread(target=threadFunc(loop,(i,loops[i]),loop.__name__))
threads.append(t)
for i in nloops:#start threads
threads[i].start()
for i in nloops:#wait for all threads finish
threads[i].join()
print 'all threads Done! at :',ctime()
if __name__ == '__main__':
main()
MAIN thread start at : Sat Jun 14 14:00:57 2014
start loop 0 at : Sat Jun 14 14:00:57 2014start loop
1start loop 2 at : Sat Jun 14 14:00:57 2014
at : Sat Jun 14 14:00:57 2014
loop 1 done! at : Sat Jun 14 14:00:59 2014
loop 2 done! at : Sat Jun 14 14:01:00 2014
loop 0 done! at : Sat Jun 14 14:01:01 2014
all threads Done! at : Sat Jun 14 14:01:01 2014
[Finished in 4.0s]
从Thread派生出一个子类,创建一个这个子类的实例
#/usr/bin/env python
#encoding:utf-8
'''使用threading:派生一个Thread的子类,创建这个子类的实例'''
import threading
from time import ctime,sleep
loops = [4,2,3]
class MyThread(threading.Thread):
"""docstring for MyThread"""
def __init__(self, func,arg,name=''):
super(MyThread, self).__init__()
self.arg = arg
self.func = func
self.name = name
def run(self):
apply(self.func,self.arg)
def loop(nloop,nsec):
print 'start loop',nloop,'at :',ctime()
sleep(nsec)
print 'loop',nloop,'done! at :',ctime()
def main():
print 'MAIN thread start at :',ctime()
threads = []
nloops = range(len(loops))
for i in nloops:#create a instance for Thread class
t = MyThread(loop,(i,loops[i]))
threads.append(t)
for i in nloops:#start threads
threads[i].start()
for i in nloops:#wait for all threads finish
threads[i].join()
print 'all threads Done! at :',ctime()
if __name__ == '__main__':
main()
MAIN thread start at : Sat Jun 14 14:02:05 2014
start loopstart loop 10 at : at : Sat Jun 14 14:02:05 2014 Sat Jun 14 14:02:05 2014
start loop 2 at : Sat Jun 14 14:02:05 2014
loop 1 done! at : Sat Jun 14 14:02:07 2014
loop 2 done! at : Sat Jun 14 14:02:08 2014
loop 0 done! at : Sat Jun 14 14:02:09 2014
all threads Done! at : Sat Jun 14 14:02:09 2014
[Finished in 4.0s]
18.5.4 斐波那契,阶乘和累加和
#/usr/bin/env python
#encoding:utf-8
import threading
from time import ctime,sleep
class MyThread(threading.Thread):
"""docstring for MyThread"""
def __init__(self, func,arg,name=''):
super(MyThread, self).__init__()
self.arg = arg
self.func = func
self.name = name
def getResult(self):
return self.res
def run(self):
print 'starting',self.name , 'at:',ctime()
self.res = apply(self.func,self.arg)
print self.name,'finished at:',ctime()
def fib(x):
sleep(0.005)
if x<2:
return 1
return (fib(x-2)+fib(x-1))
def fac(x):
sleep(0.01)
if x<2:
return 1
return (x*fac(x-1))
def sum(x):
sleep(0.1)
if x<2:
return 1
return (x+sum(x-1))
funcs = [fib,fac,sum]
n = 12
def main():
nfuncs = range(len(funcs))
print '***SINGLE THREAD***'
for i in nfuncs:
print 'starting',funcs[i].__name__,'at:',ctime()
print funcs[i](n)
print funcs[i].__name__,'finished at:',ctime()
print '\n***Mutilpe Thread***'
threads = []
for i in nfuncs:
t = MyThread(funcs[i],(n,),funcs[i].__name__)
threads.append(t)
for i in nfuncs:
threads[i].start()
for i in nfuncs:
threads[i].join()
print threads[i].getResult()
print 'All Done!'
if __name__ == '__main__':
main()
***SINGLE THREAD***
starting fib at: Sat Jun 14 14:55:36 2014
233
fib finished at: Sat Jun 14 14:55:38 2014
starting fac at: Sat Jun 14 14:55:38 2014
479001600
fac finished at: Sat Jun 14 14:55:39 2014
starting sum at: Sat Jun 14 14:55:39 2014
78
sum finished at: Sat Jun 14 14:55:40 2014
***Mutilpe Thread***
startingstarting fib at: Sat Jun 14 14:55:40 2014
fac at: Sat Jun 14 14:55:40 2014
starting sum at: Sat Jun 14 14:55:40 2014
fac finished at: Sat Jun 14 14:55:40 2014
sum finished at: Sat Jun 14 14:55:41 2014
fib finished at: Sat Jun 14 14:55:42 2014
233
479001600
78
All Done!
[Finished in 6.5s]
18.5.5 生产者--消费者问题和Queue模块
常用的queue模块的属性
queue(size) 创建一个大小为size的queue对象
qsize() 返回队列的大小
empty() 如果队列为空返回True,否则返回false
full() 如果队列已满返回True,否则返回false
put(item, block = 0) 把item放到队列中,如果给了block(不为0),函数会一直阻塞到队列中有空间为止
get(block = 0) 从队列中取一个对象,如果给了block(不为0),函数会一直阻塞到队列中有对象为止。
queue模块可以用来进行线程间通讯,让各个线程之间共享数据。
#/usr/bin/env python
#encoding:utf-8
from random import randint
from time import sleep,ctime
from Queue import Queue
from myThread import MyThread
def writeQ(queue):
print 'producing object fro Q...',
queue.put('xxx',1)
print 'size now',queue.qsize()
def readQ(queue):
val = queue.get(1)
print 'cosumed object from Q... size now',queue.qsize()
def writer(queue,loops):
for i in range(loops):
writeQ(queue)
sleep(randint(1,3))
def reader(queue,loops):
for i in range(loops):
readQ(queue)
sleep(randint(2,5))
funcs = [writer,reader]
nfunc = range(len(funcs))
def main():
nloops = randint(2,5)
q = Queue(32)
threads = []
for i in nfunc:
t = MyThread(funcs[i],(q,nloops),funcs[i].__name__)
threads.append(t)
for i in nfunc:
threads[i].start()
for i in nfunc:
threads[i].join()
print 'all done!'
if __name__ == '__main__':
main()
startingstarting reader writer at: Sat Jun 14 15:42:04 2014
at: Sat Jun 14 15:42:04 2014
producing object fro Q... size now 1
cosumed object from Q... size now 0
producing object fro Q... size now 1
cosumed object from Q... size now 0
producing object fro Q... size now 1
cosumed object from Q... size now 0
producing object fro Q... size now 1
producing object fro Q... size now 2
writer finished at: Sat Jun 14 15:42:15 2014
cosumed object from Q... size now 1
cosumed object from Q... size now 0
reader finished at: Sat Jun 14 15:42:25 2014
all done!