多线程编程

线程与Python

全局解释器锁

     Python代码的执行由Python虚拟机(解释器主循环)进行控制。在主循环中同时只能有一个控制线程在执行,尽管Python解释器中可以运行多个线程,但在任意给定时刻只有一个线程会被解释器执行。对Python虚拟机的访问是由全局解释器锁(GIL)控制的,该锁用来保证同时只能有一个线程运行。Python虚拟机将按照如下方式执行:

  1. 设置GIL
  2. 切换进一个线程去运行
  3. 执行如下操作:a.指定数量的字节码指令;b.线程主动让出控制权(可以调用time.sleep(0)来完成)
  4. 把线程设置回睡眠状态(切换出线程)
  5. 解锁GIL
  6. 重复上述步骤
     当调用外部代码时,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 模块还提供了一些函数:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值