Python之路(第四十二篇)線程相關的其餘方法、join()、Thread類的start()和run()方法的區別、守護線程

1、線程相關的其餘方法

  
  Thread實例對象的方法
    # isAlive(): 返回線程是否活動的。
    # getName(): 返回線程名。
    # setName(): 設置線程名。
  ​
  threading模塊提供的一些方法:
    # threading.currentThread(): 返回當前的線程對象。
    # threading.enumerate(): 返回一個包含正在運行的線程的list。正在運行指線程啓動後、結束前,不包括啓動前和終止後的線程。
    # threading.activeCount(): 返回正在運行的線程個數,與len(threading.enumerate())有相同的結果。
    #threading.main_thread()      返回主線程對象
    #threading.get_ident()        返回當前線程的ID,非0整數

  

例子python

  

  import time
  import threading
  ​
  def func(arg):
      time.sleep(1)
      print(arg,threading.current_thread(),threading.get_ident()) #threading.current_thread() 獲取當前進程對象,
      # threading.get_ident()獲取當前線程號
  ​
  for i in range(10):
      threading.Thread(target=func,args=(i,)).start()
  print("線程數量統計",threading.active_count()) #統計當前線程數量
  threading.current_thread().setName("主線程") #設置線程名字
  print(threading.current_thread().isAlive()) #線程是否是活動的
  print("當前線程",threading.current_thread())
  print("獲取當前線程名字",threading.current_thread().getName())
  print("線程變量列表",threading.enumerate()) #以列表的形式顯示當前全部的線程變量

  

 

 

2、線程的join()

與進程的join方法做用相似,線程的 join方法的做用是阻塞,等待子線程結束,join方法有一個參數是timeout,即若是主線程等待timeout,子線程尚未結束,則主線程強制結束子線程。bootstrap

可是python 默認參數建立線程後,無論主線程是否執行完畢,都會等待子線程執行完畢才一塊兒退出,有無join結果同樣。進程沒有join()則在執行主進程完後直接退出,而主線程是等待子線程執行完畢才一塊兒退出。多線程

 

  
  import threading
  import time
  ​
  def func(n):
      time.sleep(2)
      print("線程是%s"%n)
      global g
      g = 0
      print(g)
  ​
  if __name__ == '__main__':
      g = 100
      t_l = []
      for i in range(5):
          t = threading.Thread(target=func,args=(i,))
          t.start()
          t_l.append(t)
      print("線程數量統計1--", threading.active_count())  # 統計當前線程數量,結果是6,5個子線程加1個主線程
  ​
      for t in t_l:
          t.join()
  ​
      print('結束了')
      print("線程數量統計2--", threading.active_count())  # 統計當前線程數量,結果是1,只有一個主線程

  

3、Thread類的start()和run()方法的區別

 

start()

  
  import threading
  import time
  ​
  ​
  def add(x, y):
      for _ in range(5): # _解壓序列賦值,_表明不用關心的元素
          time.sleep(0.5)
          print("x+y={}".format(x + y))
  ​
  ​
  class MyThread(threading.Thread):
      def start(self):
          print('start-----')
          super().start() # 調用父類的start()和run()方法
  ​
      def run(self):
          print('run-----')
          super().run()  # 調用父類的start()和run()方法
  ​
  ​
  t = MyThread(target=add, name="MyThread", args=(1, 2))
  t.start()
  # t.run()
  print("====end===")

  

執行結果:app

  
  start-----
  run-----
  ====end===
  x+y=3
  x+y=3
  x+y=3
  x+y=3
  x+y=3

  

分析:能夠看出start()方法會先運行start()方法,再運行run()方法。ide

從源碼簡單追蹤下start()的調用過程:函數

  
  一、 def start(self):
          print('start-----')
          super().start() # 調用父類的start()和run()方法
  ​
  ​
  二、def start(self): #父類的start()
      _start_new_thread(self._bootstrap, ()) 
      #執行_start_new_thread找到_start_new_thread,再次找到_thread.start_new_thread,這裏是pass
      #下一步獲取self._bootstrap值找到def _bootstrap,經過self._bootstrap_inner(),最後執行了      #self.run()
      ....
   
  三、_start_new_thread = _thread.start_new_thread
   
  四、def start_new_thread(function, args, kwargs=None):
      pass
   
  五、def _bootstrap(self):
      self._bootstrap_inner()
   
  六、def _bootstrap_inner(self):
      ....
      try:
          self.run()#最終start()方法調用了run()方法
      except SystemExit:
          pass

  

 

 

run()

  
  import threading
  import time
  ​
  ​
  def add(x, y):
      for _ in range(5): # _解壓序列賦值,_表明不用關心的元素
          time.sleep(0.5)
          print("x+y={}".format(x + y))
  ​
  ​
  class MyThread(threading.Thread):
      def start(self):
          print('start-----')
          super().start() # 調用父類的start()和run()方法
  ​
      def run(self):
          print('run-----')
          super().run()  # 調用父類的start()和run()方法
  ​
  ​
  t = MyThread(target=add, name="MyThread", args=(1, 2))
  # t.start()
  t.run()
  print("====end===")

  

執行結果:spa

  
  run-----
  x+y=3
  x+y=3
  x+y=3
  x+y=3
  x+y=3
  ====end===

  

分析:運行線程的run()方法只能調用到run()方法。操作系統

從源碼簡單追蹤下runt()的調用過程:線程

  
  一、def run(self):
      print('run-----')
      super().run()  # 調用父類的start()和run()方法
  ​
  二、def __init__(self, group=None, target=None, name=None,
                   args=(), kwargs=None, *, daemon=None):
      self._target = target #這裏的_target是個子線程的函數名
      self._args = args 
      self._kwargs = kwargs
      ....
   
  三、def run(self):
      if self._target:
          self._target(*self._args, **self._kwargs) #這裏就直接執行了這個函數

  

分析:target是咱們傳入的目標函數,run()方法其實就相似一個裝飾器,最終仍是將args 和kwargs 參數傳入目標函數運行,返回結果。orm

 

繼續分析:

start()

  import threading
  import time
  ​
  ​
  def func(n):
      time.sleep(2)
      print("線程是%s" % n)
      print('子線程的ID號A', threading.current_thread().ident)
      global g
      g = 0
      print('子線程中的g', g)
  ​
  ​
  class Mythread(threading.Thread):
  ​
      def __init__(self, arg, *args, **kwargs):
          super().__init__(*args, **kwargs)
          self.arg = arg
  ​
      def start(self):
          print('start-----')
          super().start()  # 調用父類的start()和run()方法
  ​
      def run(self):
          print('run-----')
          print("類中的子線程", self.arg)
          super().run()
          print('子線程的ID號B',threading.current_thread().ident)
  ​
  ​
  if __name__ == '__main__':
      g = 100
      t1 = Mythread('hello', target=func, name="MyThread", args=('nick',))
      # 第一個參數是用在Mythread類中的,後面的3個參數用在建立的func子線程中,args必須是可迭代的
      # 這裏的func也能夠直接寫在Mythread中的run()裏,這時這裏的run()不用再繼承父類的run()
      t1.start()
      #t1.run()
      print('主線程中的g', g)
      print('主線程的ID號---', threading.current_thread().ident)

  

執行結果

  
  start-----
  run-----
  類中的子線程 hello
  線程是nick
  子線程的ID號A 19672
  子線程中的g 0
  子線程的ID號B 19672
  主線程中的g 0
  主線程的ID號--- 12056

  

分析:能夠看到這裏有主進程有子線程func()和mythread.run()屬於同一子線程,由於mythread.run()繼承父類的run()最終仍是要執行func()函數的,這裏只是在對象中多寫了幾行。

run()

  
  import threading
  import time
  ​
  ​
  def func(n):
      time.sleep(2)
      print("線程是%s" % n)
      print('子線程的ID號A', threading.current_thread().ident)
      global g
      g = 0
      print('子線程中的g', g)
  ​
  ​
  class Mythread(threading.Thread):
  ​
      def __init__(self, arg, *args, **kwargs):
          super().__init__(*args, **kwargs)
          self.arg = arg
  ​
      def start(self):
          print('start-----')
          super().start()  # 調用父類的start()和run()方法
  ​
      def run(self):
          print('run-----')
          print("類中的子線程", self.arg)
          super().run()
          print('子線程的ID號B',threading.current_thread().ident)
  ​
  ​
  if __name__ == '__main__':
      g = 100
      t1 = Mythread('hello', target=func, name="MyThread", args=('nick',))
      # 第一個參數是用在Mythread類中的,後面的3個參數用在建立的func子線程中,args必須是可迭代的
      # 這裏的func也能夠直接寫在Mythread中的run()裏,這時這裏的run()不用再繼承父類的run()
      # t1.start()
      t1.run()
      print('主線程中的g', g)
      print('主線程的ID號---', threading.current_thread().ident)

  

執行結果

  
  run-----
  類中的子線程 hello
  線程是nick
  子線程的ID號A 18332
  子線程中的g 0
  子線程的ID號B 18332
  主線程中的g 0
  主線程的ID號--- 18332

  

分析:這能夠看到,程序居然只有有個線程,那就是主線程。

 

例子

  
  import threading
  # 定義準備做爲子線程action函數
  def action(max):
      for i in range(max):
          # 直接調用run()方法時,Thread的name屬性返回的是該對象的名字
          # 而不是當前線程的名字
          # 使用threading.current_thread().name老是獲取當前線程的名字
          print(threading.current_thread().name +  " " + str(i))  # ①
  for i in range(100):
      # 調用Thread的currentThread()方法獲取當前線程
      print(threading.current_thread().name +  " " + str(i))
      if i == 20:
          # 直接調用線程對象的run()方法
          # 系統會把線程對象當成普通對象,把run()方法當成普通方法
          # 因此下面兩行代碼並不會啓動兩個線程,而是依次執行兩個run()方法
          threading.Thread(target=action,args=(100,)).run()
          threading.Thread(target=action,args=(100,)).run()

  

上面程序在建立線程對象後,直接調用了線程對象的 run() 方法,程序運行的結果是整個程序只有一個主線程。還有一點須要指出,若是直接調用線程對象的 run() 方法,則在 run() 方法中不能直接經過 name 屬性(getName() 方法)來獲取當前執行線程的名字,而是須要使用 threading.current_thread() 函數先獲取當前線程,而後再調用線程對象的 name 屬性來獲取線程的名字。

經過上面程序不難看出,啓動線程的正確方法是調用 Thread 對象的 start() 方法,而不是直接調用 run() 方法,不然就變成單線程程序了。

須要指出的是,在調用線程對象的 run() 方法以後,該線程己經再也不處於新建狀態,不要再次調用線程對象的 start() 方法。

 

注意,只能對處於新建狀態的線程調用 start() 方法。也就是說,若是程序對同一個線程重複調用 start() 方法,將引起 RuntimeError 異常。

 

總結:

從上面四個小例子,咱們能夠總結出:

  • start() 方法是啓動一個子線程

  • run() 方法並不啓動一個新線程,就是在主線程中調用了一個普通函數而已。

 

所以,若是你想啓動多線程,就必須使用start()方法。

 

 

 

4、守護線程

​ 守護線程會在"該進程內全部非守護線程所有都運行完畢後,守護線程纔會掛掉"。並非主線程運行完畢後守護線程掛掉。這一點是和守護進程的區別之處!

須要強調的是:運行完畢並不是終止運行**。

不管是進程仍是線程,都遵循:守護xxx會等待xxx運行完畢後被銷燬

 

進程與線程的守護進(線)程對比

  • 對主進程來講,運行完畢指的是主進程代碼運行完畢

  • 對主線程來講,運行完畢指的是主線程所在的進程內全部非守護線程通通運行完畢,主線程纔算運行完畢

 

守護進程:主進程代碼運行完畢,守護進程也就結束                  (守護的是主進程)

                    主進程要等非守護進程都運行完畢後再回收子進程的資源(不然會產生殭屍進程)才結束

                    主進程等子進程是由於主進程要給子進程收屍(代用wait方法向操做系統發起回收資源信號(pid號,狀態信息))

 

守護線程:非守護線程代碼運行完畢,守護線程也就結束           (守護的是非守護線程)

                     主線程在其餘非守護線程運行完畢後纔算結束(主線程的結束意味着進程的結束,守護線程在此時就會被回收)

                    強調:主線程也是非守護線程(進程包含了線程)

 

總結:

  1. 主線程活着的時候,守護線程纔會存活。主線程結束後,守護線程會自動被殺死結束運行。

  2. 主線程需等全部非守護線程退出後纔會退出,若是想要結束非守護線程,咱們必須手動找出非守護線程將其殺死。

實例

主線程啓動兩個子線程:

  • 子線程0-守護線程,運行10秒退出

  • 子線程1-非守護線程,運行1秒退出。

根據咱們上面的總結,咱們會知道:

  • 主線程啓動完子線程,等待全部非守護線程運行

  • 非守護子線程1運行1秒退出

  • 此時沒有非守護線程運行,主線程退出

  • 子線程0雖然任務還未完成,可是它是守護線程,會緊跟主線程退出。

 

例子

  
  # 守護線程
  from threading import Thread
  import time
  ​
  def func1():
      while True:
          print("in func1")
          time.sleep(5)
  ​
  def func2():
      print("in func2")
      time.sleep(1)
  ​
  t1 = Thread(target=func1,)
  t1.daemon = True
  t1.start()
  t2 = Thread(target=func2,)
  t2.start()
  print("主進程")

  

分析:這裏的t1線程做爲守護線程必定是執行不完的,由於其餘非守護線程很快執行完了,主線程就要結束了,主線程結束進程要回收資源,因此t1做爲守護線程立刻會被結束掉。

 

 

例子2

  
  ​
  from threading import Thread
  import time
  def foo():
      print(123)
      time.sleep(1)
      print("end123")
  ​
  def bar():
      print(456)
      time.sleep(3)
      print("end456")
      
  t1=Thread(target=foo)
  t2=Thread(target=bar)
  ​
  t1.daemon=True
  t1.start()
  t2.start()
  print("主線程-------")

  

分析:雖然這裏設置了t1是守護線程,可是因爲t1線程運行的時間較短,因此這裏的守護線程會完成運行,不會出現運行一半程序直接退出的狀況。

相關文章
相關標籤/搜索