18.1 引言/动机


18.2 线程和进程


18.2.1 什么是进程(重量级进程)?

计算机程序只不过是磁盘中可执行的,二进制(或其他类型)的数据,他们只有在被读取到内存中,被操作系统调用时才开始他们的生命期,进程是程序的一次执行,每个进程都有自己的地址空间,内存,数据栈以及其他记录其运行轨迹的赋值数据,操作系统管理在其上运行的所有进程,并为这些进程公平地分配时间,进程可以通过fork和spawn操作来完成其他任务,其之间用进程间通讯(IPC)


18.2.2 什么是线程(轻量级进程)?

线程跟进程有些相似,不同的是,所有线程运行在同一个进程中,共享相同运行环境,他们可以想象成是在主进程或"主线程"中并行运行的"迷你进程".


一个进程中的各个线程之间共享同一片数据空间,从而更方便的共享数据和相互通讯,线程一般都是并行执行.


18.3 Python,线程和全局解释器锁


18.3.1 全局解释器锁(GIL)


Python代码的执行由Python虚拟机(也叫解释器主循环)来控制,对虚拟机的访问由卷曲解释器锁(GIL)来控制,正式这个锁能保证同一时刻只有一个线程在运行


在多线程环境中,Python虚拟机按一下方式执行:


1.设置GIL

2.切换到一个线程去运行

3.运行:1.执行数量的字节码指令 或2.线程主动让出控制(调用time.sleep(0))

4.把线程设置为睡眠状态

5.解锁GIL

6.再次重复以上所有步骤


18.3.2 退出线程

当一个线程结束计算,它就退出了,线程可以调用thread.exit()之类的退出函数.或标准的sys.exit()或抛出一个SystemExit异常等,不过你不可以直接杀掉一个线程


主线程应该是一个好的管理者,它了解每个线程都做些什么事,线程都需要什么数据和参数,以及在线程结束的时候,他们提供了什么结果,这样主线程就可以把各个线程的结果组成一个有意义的最后结果.


18.3.3 在Python中使用线程


在解释器里判断线程是否可用,只要导入thread模块后未报错即表示线程可用


>>> import thread

>>>


18.3.4 没有线程支持的情况


例,单线程中运行的循环()


在单线程中顺序执行两个循环,一定要一个循环结束,另一个才能开始,总时间是各个循环运行时间之和

# vi onethr.py

-----------------------------------------

#!/usr/bin/env python


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()

-----------------------------------------


输出:

# python onethr.py

starting at: Wed Dec  4 07:01:26 2013

start loop 0 at: Wed Dec  4 07:01:26 2013

loop 0 done at: Wed Dec  4 07:01:30 2013

start loop 1 at: Wed Dec  4 07:01:30 2013

loop 1 done at: Wed Dec  4 07:01:32 2013

all DONE at: Wed Dec  4 07:01:32 2013


假定loop0()和loop1()例做的不是睡眠,而是各自独立,不相关的运算,各自运算结果到最后将汇总成一个最终的结果


18.3.5 Python的threading模块


Python提供了thread,threading和Queue等多线程编程模块




注:避免使用thread模块

因为使用thread模块里的属性有可能会与threading出现冲突,threading较thread对线程支持更加完善'


18.4 thread模块


thread模块和锁对象


函数描述

thread模块函数

start_new_thread(function,

args, kwargs=None)产生一个新的进程,在新线程中用指定的参数和可选kwargs来调用这个函数


allocate_lock()分配一个LookType类型的锁对象

exit()让线程退出


LockType类型锁对象方法

acquire(wait=None)尝试获得锁对象

locked()如果获取了锁对象返回True,否则返回False

release()释放锁


例,这儿执行的是和onethr.py中一样的循环,不同的是,这次我们使用的是thread模块提供的简单的多线程机制,两个循环

# vi mtsleep1.py

------------------------------

#!/usr/bin/env python


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()

   thread.start_new_thread(loop0, ())

   thread.start_new_thread(loop1, ())

   sleep(6)

   print 'all DONE at:', ctime()


if __name__=='__main__':

   main()

-------------------------------


这个程序的输出与之前的输出大不相同,之前是运行了6,7秒,现在是4秒

因为睡眠4秒和2秒的代码现在是并发执行的,这样就使得运行时间被缩短了

在这里,我们使用了sleep()函数作为我们的同步机制


# python mtsleep1.py

-----------------------------------------

starting at: Fri Dec 20 23:56:42 2013

start loop 1 at: Fri Dec 20 23:56:42 2013

start loop 0 at: Fri Dec 20 23:56:42 2013

loop 1 done at: Fri Dec 20 23:56:44 2013

loop 0 done at: Fri Dec 20 23:56:46 2013

all DONE at: Fri Dec 20 23:56:48 2013

------------------------------------------


例,通过使用锁来完成任务


这里,使用锁比mtsleep1.py那里在主线程中使用sleep()函数更合理


# vi mtsleep2.py

------------------------------------

#!/usr/bin/env python


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]))


   for i in nloops:

       while locks[i].locked(): pass


   print 'all DONE at:', ctime()


if __name__ == '__main__':

   main()

------------------------------------


# python mtsleep2.py

---------------------------

starting at: Sat Dec 21 01:13:06 2013

start loop 1 at: Sat Dec 21 01:13:06 2013

start loop 0 at: Sat Dec 21 01:13:06 2013

loop 1 done at: Sat Dec 21 01:13:08 2013

loop 0 done at: Sat Dec 21 01:13:10 2013

all DONE at: Sat Dec 21 01:13:10 2013

---------------------------


在线程结束时,线程要自己去做解锁操作,最后一个循环只是坐在那一直等,直到两个锁都被解锁为止才继续运行,由于我们顺序检查每一个锁,所以我们可能会要长时间地等待运行时间长且放在前面的线程,当这些线程的锁释放后,后面的锁可能早就释放了,结果主线程只能毫不停歇地完成对后面这些锁的检查


thread模块只是作为演示,在使用多线程程序应该使用更高级别的模块,如threading等


18.5 threading模块


接下来,我们要介绍的是更高级别的threading模块,他不仅提供了Thread类,还提供了各种非常好用的同步机制


这里我们不在提到锁原语,而Thread类也有某种同步机制


threading模块对象


threading模块对象描述

Thread标识一个线程的执行对象

RLock锁原语对象(跟thread模块里的锁对象相同)

Condition条件变量对象能让一个线程停下来,等待其他线程满足了某个条件,如,状态的改变或值的改变

Event通用的条件变量,多个线程可以等待某个事件的发生,在事件发生后,所有的线程都会被激活.

Semaphore为等待锁的线程提供一个类似"等候室"的结构

BoundedSemaphore与Semaphpre类似,只是它不允许超过初始值

Timer与Thread相似,只是,它要等待一段时间后才开始运行.


核心提示:守护线程

守护线程一般是一个等待客户请求的服务器,如果没有客户提出请求,它就在那等着,如果你设定一个线程为守护线程,就标识你在说这个线程是不重要的,在进程退出的时候,不用等待这个线程退出

如果你的主线程要退出的时候,不用等待那些子线程完成,那就设定这些线程的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


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


例,使用thread模块,threading模块的Thread类有一个join()函数,允许主线程等待线程的结束

# vi mtsleep3.py

-------------------------------

#!/usr/bin/env python


import threading

from time import sleep,ctime


loops = [4,2]


def loop(nloop,nsec):

   print 'start loop', nloop, 'at:', ctime()

#!/usr/bin/env python


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, 'done 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()

-------------------------------


# python mtsleep3.py

-------------------------------

starting at: Sat Dec 21 12:14:57 2013

start loop 0 at: Sat Dec 21 12:14:57 2013

start loop 1 at: Sat Dec 21 12:14:57 2013

loop 1 done at: Sat Dec 21 12:14:59 2013

loop 0 done at: Sat Dec 21 12:15:01 2013

all DONE at: Sat Dec 21 12:15:01 2013

--------------------------------


所有线程都创建了之后,再一起调用start()函数启动,而不是创建一个启动一个,而且不用再管理一堆锁,只要简单对每个线程调用join()函数就可以


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


此例中,我们传了一个可调用的类的实例,而不是仅传一个函数,相对mtsleep3.py中的方法来说,这样做更具面向对象的概念


# vi mtsleep4.py

------------------------------

#!/usr/bin/env python


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):

       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 '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()

------------------------------


# python mtsleep4.py

---------------------------

starting at: Sat Dec 21 18:51:07 2013

start loop 0 at: Sat Dec 21 18:51:07 2013

start loop 1 at: Sat Dec 21 18:51:07 2013

loop 1 done at: Sat Dec 21 18:51:09 2013

loop 0 done at: Sat Dec 21 18:51:11 2013

all DONE at: Sat Dec 21 18:51:11 2013

----------------------------


18.5.4 斐波那契,阶乘和累加和


从Thread派生出一个子类,创建一个这个子类的实例


例,我们现在要子类化Thread类,而不是创建它的实例


# vi mtsleep5.py

-------------------------------

#!/usr/bin/env python


import threading

from time import sleep, time, ctime


loops = [ 4, 2 ]


class MyThread(threading.Thread):

   '''

   MyThread derives from the threading.Thread baseclass.

   Rather than a callable class, the run() method is

   automatically invoked when the thread begins execution.

   '''

   # constructor

   def __init__(self, func, args, name=''):

       threading.Thread.__init__(self)

       self.name = name

       self.setFunc(func, args)


   def setFunc(self, func, args):

       '''

       setFunc() is the method that sets the function

       to be called as well as its arguments

       '''

       self.func = func

       self.args = args


   def getResult(self):

       '''

       getResult() provides the return value

       from the function call

       '''

       return self.res


   def run(self):

       '''

       save the return value from the function call

       into an instance var rather than returning it

       '''

       # * 1.6 * self.res = self.func(*self.args)

       self.res = apply(self.func, self.args)


# loop() is the same as before

def loop(nloop, nsec):

   print 'start loop', nloop, 'at:', ctime(time())

   sleep(nsec)

   print 'loop', nloop, 'done at:', ctime(time())


def main():

   # variable setup

   print 'starting threads...'

   threads = []

   nloops = range(len(loops))


   # allocate (but do not start) threads

   for i in nloops:

       t = MyThread(loop, (i, loops[i]), loop.__name__)

       threads.append(t)


   # start threads

   for i in nloops:

       threads[i].start()


   # wait for all threads to complete

   for i in nloops:

       threads[i].join()


   print 'all DONE'


if __name__ == '__main__':

   main()


-------------------------------


# python mtsleep5.py

------------------------------

starting threads...

start loop 0 at: Sat Dec 21 23:44:07 2013

start loop 1 at: Sat Dec 21 23:44:07 2013

loop 1 done at: Sat Dec 21 23:44:09 2013

loop 0 done at: Sat Dec 21 23:44:11 2013

all DONE

-----------------------------


为了让mtsleep5.py中,Thread的子类更为通用,我们把子类单独放在一个模块中,加上一个getResult()函数用以返回函数的运行结果


例,myThread子类化Thread


# vi myThread.py

------------------------------------

#!/usr/bin/env python


import threading

from time import time, ctime


class MyThread(threading.Thread):

   def __init__(self, func, args, name=''):

       threading.Thread.__init__(self)

       self.name = name

       self.func = func

       self.args = args


   def getResult(self):

       return self.res


   def run(self):

       print 'starting', self.name, 'at:', \

   ctime()

       self.res = apply(self.func, self.args)

       print self.name, 'finished at:', \

   ctime()


------------------------------------


例,斐波那契,阶乘和累加和


在这个多线程程序中,我们会分别在单线程和多线程环境中,运行三个递归函数


# vi mtfacfib.py

------------------------------

#!/usr/bin/env python


from myThread import MyThread

from time import time, ctime, sleep


def fib(x):

   sleep(0.005)

   if x < 2: return 1

   return (fib(x-2) + fib(x-1))


def fac(x):

   sleep(0.1)

   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(time())

       print funcs[i](n)

       print funcs[i].__name__, 'finished at:', \

   ctime(time())


   print '\n*** MULTIPLE THREADS'

   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 at:', ctime(time())


if __name__ == '__main__':

   main()


------------------------------


# python mtfacfib.py

-----------------------------------------

*** SINGLE THREAD

starting fib at: Sat Dec 21 23:55:31 2013

233

fib finished at: Sat Dec 21 23:55:34 2013

starting fac at: Sat Dec 21 23:55:34 2013

479001600

fac finished at: Sat Dec 21 23:55:35 2013

starting sum at: Sat Dec 21 23:55:35 2013

78

sum finished at: Sat Dec 21 23:55:37 2013


*** MULTIPLE THREADS

starting fib at: Sat Dec 21 23:55:37 2013

starting fac at: Sat Dec 21 23:55:37 2013

starting sum at: Sat Dec 21 23:55:37 2013

fac finished at: Sat Dec 21 23:55:38 2013

sum finished at: Sat Dec 21 23:55:38 2013

fib finished at: Sat Dec 21 23:55:39 2013

233

479001600

78

all DONE at: Sat Dec 21 23:55:39 2013

------------------------------------------


18.5.5 threading模块中的其他函数


函数描述

activeCount()当前活动的线程对象的数量

currentThread()返回当前线程对象

enumerate()返回当前活动线程的列表

settrace(func)为所有线程设置一个跟踪函数

setprofile(func) 为所有线程设置一个profile函数


18.5.5 生产者-消费者问题和Queue模块


这个实现中使用了Queue对象和随机地生产(和消耗)货物的方式,生产者和消费者相互独立并且并发的运行


# vi prodcons.py

----------------------------------

#!/usr/bin/env python


from random import randint

from time import time, ctime, sleep

from Queue import Queue

from myThread import MyThread


def writeQ(queue):

   print 'producing object for Q...',

   queue.put('xxx', 1)

   print "size now", queue.qsize()


def readQ(queue):

   val = queue.get(1)

   print 'consumed 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)

nfuncs = range(len(funcs))


def main():

   nloops = randint(2, 5)

   q = Queue(32)


   threads = []

   for i in nfuncs:

       t = MyThread(funcs[i], (q, nloops), \

           funcs[i].__name__)

       threads.append(t)


   for i in nfuncs:

       threads[i].start()


   for i in nfuncs:

       threads[i].join()


   print 'all DONE'


if __name__ == '__main__':

   main()


----------------------------------


# python prodcons.py

-----------------------------------------

starting writer at: Sun Dec 22 00:00:36 2013

producing object for Q... size now 1

starting reader at: Sun Dec 22 00:00:36 2013

consumed object from Q... size now 0

producing object for Q... size now 1

consumed object from Q... size now 0

producing object for Q... size now 1

consumed object from Q... size now 0

writer finished at: Sun Dec 22 00:00:42 2013

reader finished at: Sun Dec 22 00:00:46 2013

all DONE

-------------------------------------------


本例中,一个要完成多项任务的程序,可以考虑没有任务使用一个线程,这样的程序在设计上相对于单线程做所有事的程序来说,更为清晰


18.6 相关模块


模块描述

thread基本的,底级别的线程模块

threading高级别的线程和同步对象

Queue供多线程使用的同步先进先出(FIFO)队列

mutex互斥对象

SocketServer具有线程控制的TCP和UDP管理