线程与Python
全局解释器锁
Python代码的执行由Python虚拟机(解释器主循环)进行控制。在主循环中同时只能有一个控制线程在执行,尽管Python解释器中可以运行多个线程,但在任意给定时刻只有一个线程会被解释器执行。对Python虚拟机的访问是由全局解释器锁(GIL)控制的,该锁用来保证同时只能有一个线程运行。Python虚拟机将按照如下方式执行:
- 设置GIL
- 切换进一个线程去运行
- 执行如下操作:a.指定数量的字节码指令;b.线程主动让出控制权(可以调用time.sleep(0)来完成)
- 把线程设置回睡眠状态(切换出线程)
- 解锁GIL
- 重复上述步骤
退出线程
当一个线程完成函数的执行时,它就会退出。此外还可以通过调用诸如thread.exit()之类的退出函数,或者sys.exit()之类的退出Python进程的标准方法,亦或是抛出SystemExit异常,来使线程退出。但是,你不能直接“终止”一个线程。
Python的多线程模块
Python提供了多个模块来支持多线程,包括thread、threading和Queue模块等。thread提供了基本的县城和锁定支持,threading提供了更高级别、功能更全面的线程管理。Queue模块,用户可以创建一个队列数据结构,用于在多线程之间进行共享。
thread模块
除了派生线程以外,thread还提供了基本的同步数据结构,称为锁对象(原语锁,互斥锁)。
import _thread
from time import sleep,ctime
def loop0():
print("start loop0 at:"+str(ctime()))
sleep(4)
print("loop0 done at:"+str(ctime()))
def loop1():
print("start loop1 at:" + str(ctime()))
sleep(2)
print("loop1 done at:" + str(ctime()))
def main():
print("start at:"+str(ctime()))
_thread.start_new_thread(loop0,())
_thread.start_new_thread(loop1,())
sleep(6)
print("all done at:",ctime())
if __name__=='__main__':
main()
注意Python3中没有thread模块,为了兼容改为_thread模块。
调用 _thread 模块中的start_new_thread()函数来产生新线程。语法如下:
_thread.start_new_thread ( function, args[, kwargs] )
参数说明:
- function - 线程函数。
- args - 传递给线程函数的参数,他必须是个tuple类型。
- kwargs - 可选参数。
其中第二个参数不可省,若没有需要传递进一个空元组。
我们在主线程中写入了一个sleep(6)来作为同步机制,因为我们知道loop0和loop1(所有子线程)会在主线程计时到6秒之前完成。但是像这样用sleep来进行线程同步是不可靠的,如果循环有独立且不同的执行时间,我们可能会过早或过晚退出主线程。故我们要引出“锁”,即线程同步。使用 _Thread 对象的 lock 和 rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间.考虑这样一种情况:一个列表里所有元素都是0,线程"set"从后向前把所有元素改成1,而线程"print"负责从前往后读取列表并打印。
那么,可能线程"set"开始改的时候,线程"print"便来打印列表了,输出就成了一半0一半1,这就是数据的不同步。为了避免这种情况,引入了锁的概念。锁有两种状态——锁定和未锁定。每当一个线程比如"set"要访问共享数据时,必须先获得锁定;如果已经有别的线程比如"print"获得锁定了,那么就让线程"set"暂停,也就是同步阻塞;等到线程"print"访问完毕,释放锁以后,再让线程"set"继续。经过这样的处理,打印列表时要么全部输出0,要么全部输出1,不会再出现一半0一半1的尴尬场面。
修改上述代码如下:
loops=[4,2]
def loop(nloop,nsec,lock):
print("start loop "+str(nloop)+"at:"+str(ctime()))
sleep(nsec)
print("loop "+str(nloop)+"done at:"+str(ctime()))
lock.release() #每个线程分配一个锁,线程的sleep时间到了,就释放锁,向主线程表明该线程已经完成
def main():
print("start at:"+str(ctime()))
locks=[]
nloops=range(len(loops))
for i in nloops: #创建锁列表
lock=_thread.allocate_lock() #创建锁对象
lock.acquire() #获取每个锁,相当于把锁锁上
locks.append(lock) #将锁添加到列表locks中
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:"+str(ctime()))
threading模块
注:由于Thread()类包含某种同步机制,所以锁原语的显式使用不再是必需的了。
Thread类
是threading模块主要的执行对象,它有thread模块中没有的很多函数。
使用Thread类有多种方法来创建线程,比较相似的有下列三种:
- 创建Thread的实例,传给它一个函数
- 创建Thread的实例,传给它一个可调用的类实例
- 派生Thread的子类,并创建子类的实例
创建Thread的实例,传给它一个函数
loops=[4,2]
def loop(nloop,nsec):
print("start loop "+str(nloop)+"at:"+str(ctime()))
sleep(nsec)
print("loop "+str(nloop)+"done at:"+str(ctime()))
def main():
print("starting at:"+str(ctime()))
threads=[]
nloops=range(len(loops)) #用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:"+str(ctime()))
说明:当实例化每个Thread对象时,把函数(target)和参数(args)传进去,然后得到返回的Thread实例。实例化Thread和调用thread.start_new_thread()的最大区别是新线程不会立即开始执行。当所有线程分配完成后,通过调用每个线程的start()方法让它们开始执行。相比于管理一组锁而言,这里只需要为每个线程调用join()方法即可,join()等待至线程中止。这阻塞调用线程直至线程的join() 方法被调用中止-正常退出或者抛出未处理的异常-或者是可选的超时发生。注意,主线程如果还有其他事情要去做,就可以不调用joion()。join()方法只有在你需要等待线程完成的时候才是有用的。
创建Thread的实例,传给它一个可调用的类实例
loops=[4,2]
class ThreadFunc(object):
def __init__(self,func,args,name=''):
self.name=name
self.func=func
self.args=args
def __call__(self): #__call__的作用是使类的实例(对象)能够像函数一样被调用
self.func(*self.args)
def loop(nloop,nsec):
print("start loop "+str(nloop)+"at:"+str(ctime()))
sleep(nsec)
print("loop "+str(nloop)+"done at:"+str(ctime()))
def main():
print("starting at:"+str(ctime()))
threads=[]
nloops=range(len(loops)) #用loops[]的长度创建一个整数列表,用于遍历所有线程
for i in nloops: #为每一个线程创建实例对象
t=threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) #这里__name__用于指定调入的方法是loop
threads.append(t)
for i in nloops: #开始每一个线程
threads[i].start()
for i in nloops: #等待
threads[i].join #直至启动的线程终止前一直挂起
print("all DONE at:"+str(ctime()))
派生Thread的子类,并创建子类的实例
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 "+str(nloop)+"at:"+str(ctime()))
sleep(nsec)
print("loop "+str(nloop)+"done at:"+str(ctime()))
def main():
print("starting at:"+str(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:"+str(ctime()))
if __name__=='__main__':
main()
将MyThread类修改,添加调试信息并将其存储为一个名为myThread的独立模块,使MyThread更加一般化。该模块除了简单的调用函数外,还将结果保存在实例属性self.res中,并创建一个新的方法getResult()来获取这个值。
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:"+str(ctime()))
self.res=self.func(*self.args)
print(self.name+"finished at:"+str(ctime()))
除了各种同步和线程对象外,threading 模块还提供了一些函数: