• Python之路(第四十二篇)线程相关的其他方法、join()、Thread类的start()和run()方法的区别、守护线程


    一、线程相关的其他方法

      
      Thread实例对象的方法
        # isAlive(): 返回线程是否活动的。
        # getName(): 返回线程名。
        # setName(): 设置线程名。
      ​
      threading模块提供的一些方法:
        # threading.currentThread(): 返回当前的线程对象。
        # threading.enumerate(): 返回一个包含正在运行的线程的list。正在运行指线程启动后、结束前,不包括启动前和终止后的线程。
        # threading.activeCount(): 返回正在运行的线程个数,与len(threading.enumerate())有相同的结果。
        #threading.main_thread()      返回主线程对象
        #threading.get_ident()        返回当前线程的ID,非0整数
    

      

    例子

      

      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()) #以列表的形式显示当前所有的线程变量
    

      

     

    二、线程的join()

    与进程的join方法作用类似,线程的 join方法的作用是阻塞,等待子线程结束,join方法有一个参数是timeout,即如果主线程等待timeout,子线程还没有结束,则主线程强制结束子线程。

    但是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,只有一个主线程
    

      

    三、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===")
    

      

    执行结果:

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

      

    分析:可以看出start()方法会先运行start()方法,再运行run()方法。

    从源码简单追踪下start()的调用过程:

      
      1、 def start(self):
              print('start-----')
              super().start() # 调用父类的start()和run()方法
      ​
      ​
      2、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()
          ....
       
      3、_start_new_thread = _thread.start_new_thread
       
      4、def start_new_thread(function, args, kwargs=None):
          pass
       
      5、def _bootstrap(self):
          self._bootstrap_inner()
       
      6、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===")
    

      

    执行结果:

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

      

    分析:运行线程的run()方法只能调用到run()方法。

    从源码简单追踪下runt()的调用过程:

      
      1、def run(self):
          print('run-----')
          super().run()  # 调用父类的start()和run()方法
      ​
      2、def __init__(self, group=None, target=None, name=None,
                       args=(), kwargs=None, *, daemon=None):
          self._target = target #这里的_target是个子线程的函数名
          self._args = args 
          self._kwargs = kwargs
          ....
       
      3、def run(self):
          if self._target:
              self._target(*self._args, **self._kwargs) #这里就直接执行了这个函数
    

      

    分析:target是我们传入的目标函数,run()方法其实就类似一个装饰器,最终还是将args 和kwargs 参数传入目标函数运行,返回结果。

    继续分析:

    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()方法。

    四、守护线程

    ​ 守护线程会在"该进程内所有非守护线程全部都运行完毕后,守护线程才会挂掉"。并不是主线程运行完毕后守护线程挂掉。这一点是和守护进程的区别之处!

    需要强调的是:运行完毕并非终止运行**。

    无论是进程还是线程,都遵循:守护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线程运行的时间较短,所以这里的守护线程会完成运行,不会出现运行一半程序直接退出的情况。

     

  • 相关阅读:
    蚂蚁金服SOFAMesh在多语言上的实践
    2018第48周日
    git只拉取github部分代码的方法
    深入理解brew link命令
    openssl/ssl.h file not found
    react热加载失败
    pycharm批量清楚pyc、$py.class文件
    Hash history cannot PUSH the same path; a new entry will not be added to the history stack
    JavaScript indexOf() 方法,获取元素的位置;Object.keys()获取对象的所有key的数组
    JavaScript删除数组里的某个元素
  • 原文地址:https://www.cnblogs.com/Nicholas0707/p/10930083.html
Copyright © 2020-2023  润新知