python高級之多線程

多線程

  1. 什麼是多線程

    多線程類似於同時執行多個不同程序,多線程運行有如下優點:

    • 使用線程可以把佔據長時間的程序中的任務放到後臺去處理。
    • 用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度
    • 程序的運行速度可能加快
    • 在一些等待的任務實現上如用戶輸入、文件讀寫和網絡收發數據等,線程就比較有用了。在這種情況下我們可以釋放一些珍貴的資源如內存佔用等等。

    每個線程都有他自己的一組CPU寄存器,稱爲線程的上下文,該上下文反映了線程上次運行該線程的CPU寄存器的狀態。
    指令指針和堆棧指針寄存器是線程上下文中兩個最重要的寄存器,線程總是在進程得到上下文中運行的,這些地址都用於標誌擁有線程的進程地址空間中的內存。

    Python的標準庫提供了兩個模塊:_thread和threading,_thread是低級模塊,threading是高級模塊,對_thread進行了封裝。絕大多數情況下,我們只需要使用threading這個高級模塊。

    由於任何進程默認就會啓動一個線程,我們把該線程稱爲主線程,主線程又可以啓動新的線程,Python的threading模塊有個current_thread()函數,它永遠返回當前線程的實例。主線程實例的名字叫MainThread,子線程的名字在創建時指定,如果不起名字Python就自動給線程命名爲Thread-1,Thread-2……

    threading用於提供線程相關的操作,線程是應用程序中工作的最小單元。python當前版本的多線程庫沒有實現優先級、線程組,線程也不能被停止、暫停、恢復、中斷。

    threading模塊提供的類:
      Thread, Lock, Rlock, Condition, [Bounded]Semaphore, Event, Timer, local。

    threading 模塊提供的常量:

    threading.TIMEOUT_MAX 設置threading全局超時時間。

  2. 開啓線程的兩種方式-函數

     import threading
     import time
     #方法一:將要執行的方法作爲參數傳給Thread的構造方法
     
     def action(arg):
         time.sleep(1)
         print(threading.current_thread())
     
         print('the arg is:{0}'.format(arg))
     
     for i in range(4):
         t =threading.Thread(target=action,args=(i,))
         t.start()
     
     if __name__ == '__main__':
         print(threading.current_thread())
         print('main thread end!')
    
  3. 開啓線程的兩種方式-用類來包裝線程對象

     class MyThread(threading.Thread):
         def __init__(self,arg):
             super(MyThread, self).__init__()#注意:一定要顯式的調用父類的初始化函數。
             self.arg=arg
         def run(self):#定義每個線程要運行的函數
             time.sleep(1)
             print 'the arg is:%s\r' % self.arg
     
     for i in xrange(4):
         t =MyThread(i)
         t.start()
    

    Thread類

    構造方法:

    • Thread(group=None, target=None, name=None, args=(), kwargs={})

    • group: 線程組,目前還沒有實現,庫引用中提示必須是None;

    • target: 要執行的方法;

    • name: 線程名;

    • args/kwargs: 要傳入方法的參數。

    實例方法:

    • isAlive(): 返回線程是否在運行。正在運行指啓動後、終止前。

    • get/setName(name): 獲取/設置線程名。

    • start(): 線程準備就緒,等待CPU調度

    start(): 啓動線程。

    join([timeout]): 阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的timeout(可選參數)。

  4. threading 模塊提供的常用方法:

    • threading.currentThread(): 返回當前的線程變量。
    • threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。
    • threading.activeCount(): 返回正在運行的線程數量,與len(threading.enumerate())有相同的結果。
  5. 後臺線程和前臺線程

    is/setDaemon(bool): 獲取/設置是後臺線程(默認前臺線程(False))。(在start之前設置)

    1. 如果是後臺線程,主線程執行過程中,後臺線程也在進行,主線程執行完畢後,後臺線程不論成功與否,主線程和後臺線程均停止

    2. 如果是前臺線程,主線程執行過程中,前臺線程也在進行,主線程執行完畢後,等待前臺線程也執行完成後,程序停止

       if __name__ == '__main__':
           for i in range(4):
               t = MyThread(i)
               t.setDaemon(True)
               t.start()
       
           print('main thread end!')
      
  6. join()

    join()阻塞當前上下文環境的線程,直到調用此方法的線程終止或到達指定的timeout,即使設置了setDeamon(True)主線程依然要等待子線程結束。

    線程必須先start()然後再join()

    錯誤的做法是

     if __name__ == '__main__':
         for i in range(4):
             t = MyThread(i)
             t.start()
             t.join()
    

    可以看出此時,程序只能順序執行,每個線程都被上一個線程的join阻塞,使得“多線程”失去了多線程意義。

    ☝️這樣

     if __name__ == '__main__':
         th=[]
         for i in range(4):
             t = MyThread(i)
             th.append(t)
             t.start()
     
     
         for tt in th:
             tt.join()
         #設置join之後,主線程等待子線程全部執行完成後或者子線程超時後,主線程才結束
         print('main thread end!')
    
  7. 線程同步-Lock、Rlock類

    由於線程之間隨機調度:某線程可能在執行n條後,CPU接着執行其他線程。爲了多個線程同時操作一個內存中的資源時不產生混亂,我們使用鎖。

    Lock(指令鎖)是可用的最低級的同步指令。Lock處於鎖定狀態時,不被特定的線程擁有。Lock包含兩種狀態——鎖定和非鎖定,以及兩個基本的方法。

    可以認爲Lock有一個鎖定池,當線程請求鎖定時,將線程至於池中,直到獲得鎖定後出池。池中的線程處於狀態圖中的同步阻塞狀態。

    RLock(可重入鎖)是一個可以被同一個線程請求多次的同步指令。RLock使用了“擁有的線程”和“遞歸等級”的概念,處於鎖定狀態時,RLock被某個線程擁有。擁有RLock的線程可以再次調用acquire(),釋放鎖時需要調用release()相同次數。

    可以認爲RLock包含一個鎖定池和一個初始值爲0的計數器,每次成功調用 acquire()/release(),計數器將+1/-1,爲0時鎖處於未鎖定狀態。

    實例方法:

    • acquire([timeout]): 嘗試獲得鎖定。使線程進入同步阻塞狀態。

    • release(): 釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。

      import threading
      import time
      
      
      # 方法一:將要執行的方法作爲參數傳給Thread的構造方法
      count=0
      lock = threading.RLock()
      def action(arg):
          lock.acquire()
          time.sleep(1)
          global count
          count+=1
          
          print(threading.current_thread())
          count-=1
          print('the arg is:{0},count is:{1}'.format(arg,count))
          lock.release()
      ths=[]
      for i in range(4):
      
          t =threading.Thread(target=action,args=(i,))
          # t.setDaemon(True)
          ths.append(t)
      
      for tt in ths:
          tt.start()
      
      for tt in ths:
          tt.join()
      
      if __name__ == '__main__':
          # print(threading.current_thread())
          # print(threading.enumerate())
          # print(threading.activeCount())
          print('main thread end!')
      
  8. Lock對比Rlock

     import threading
     lock = threading.Lock() #Lock對象
     lock.acquire()
     lock.acquire()  #產生了死鎖。
     lock.release()
     lock.release()
     print lock.acquire()
      
      
     import threading
     rLock = threading.RLock()  #RLock對象
     rLock.acquire()
     rLock.acquire() #在同一線程內,程序不會堵塞。
     rLock.release()
     rLock.release()
    
  9. Condition類

    Condition(條件變量)通常與一個鎖關聯。需要在多個Contidion中共享一個鎖時,可以傳遞一個Lock/RLock實例給構造方法,否則它將自己生成一個RLock實例。

    可以認爲,除了Lock帶有的鎖定池外,Condition還包含一個等待池,池中的線程處於等待阻塞狀態,直到另一個線程調用notify()/notifyAll()通知;得到通知後線程進入鎖定池等待鎖定。

    構造方法:
    Condition([lock/rlock])

    實例方法:

    • acquire([timeout])/release(): 調用關聯的鎖的相應方法。

    • wait([timeout]): 調用這個方法將使線程進入Condition的等待池等待通知,並釋放鎖。使用前線程必須已獲得鎖定,否則將拋出異常。

    • notify(): 調用這個方法將從等待池挑選一個線程並通知,收到通知的線程將自動調用acquire()嘗試獲得鎖定(進入鎖定池);其他線程仍然在等待池中。調用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。

    • notifyAll(): 調用這個方法將通知等待池中所有的線程,這些線程都將進入鎖定池嘗試獲得鎖定。調用這個方法不會釋放鎖定。使用前線程必須已獲得鎖定,否則將拋出異常。

        # encoding: UTF-8
        import threading
        import time
        
        # 商品
        product = None
        # 條件變量
        con = threading.Condition()
        
        
        # 生產者方法
        def produce():
            global product
        
            if con.acquire():
                while True:
                    if product is None:
                        print ('produce...')
                        product = 'anything'
        
                        # 通知消費者,商品已經生產
                        con.notify()
        
                    # 等待通知
                    # con.wait()
                    time.sleep(2)
        
        
        # 消費者方法
        def consume():
            global product
        
            if con.acquire():
                while True:
                    if product is not None:
                        print('consume...')
                        product = None
        
                        # 通知生產者,商品已經沒了
                        con.notify()
        
                    # 等待通知
                    con.wait()
                    time.sleep(2)
        
        
        t1 = threading.Thread(target=produce)
        t2 = threading.Thread(target=consume)
        t2.start()
        t1.start()
      

      生產者消費者模型

        import threading
        import time
        
        condition = threading.Condition()
        products = 0
        
        class Producer(threading.Thread):
            def run(self):
                global products
                while True:
                    if condition.acquire():
                        if products < 10:
                            products += 1
                            print("Producer(%s):deliver one, now products:%s" %(self.name, products))
                            condition.notify()#不釋放鎖定,因此需要下面一句
                            condition.release()
                        else:
                            print("Producer(%s):already 10, stop deliver, now products:%s" %(self.name, products))
                            condition.wait()#自動釋放鎖定
                        time.sleep(2)
        
        class Consumer(threading.Thread):
            def run(self):
                global products
                while True:
                    if condition.acquire():
                        if products > 1:
                            products -= 1
                            print("Consumer(%s):consume one, now products:%s" %(self.name, products))
                            condition.notify()
                            condition.release()
                        else:
                            print("Consumer(%s):only 1, stop consume, products:%s" %(self.name, products))
                            condition.wait()
                        time.sleep(2)
        
        if __name__ == "__main__":
            for p in range(0, 2):
                p = Producer()
                p.start()
        
            for c in range(0, 3):
                c = Consumer()
                c.start()
      

      condition.notifyAll()

        import threading
      
        alist = None
        condition = threading.Condition()
        
        
        def doSet():
            if condition.acquire():
                while alist is None:
                    condition.wait()
                for i in range(len(alist))[::-1]:
                    alist[i] = 1
                    print(alist[i])
                condition.notify()
                condition.release()
        
        
        def doPrint():
            if condition.acquire():
                while alist is None:
                    condition.wait()
                for i in alist:
                    print(i)
        
                print()
                condition.notify()
                condition.release()
        
        
        def doCreate():
            global alist
            if condition.acquire():
                if alist is None:
                    alist = [0 for i in range(10)]
                    condition.notifyAll()
                condition.release()
        
        
        tset = threading.Thread(target=doSet, name='tset')
        tprint = threading.Thread(target=doPrint, name='tprint')
        tcreate = threading.Thread(target=doCreate, name='tcreate')
        tset.start()
        tprint.start()
        tcreate.start()
      
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章