一、线程相关的其他方法

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

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

  1. Python之路【第十二篇】:JavaScrpt -暂无内容-待更新

    Python之路[第十二篇]:JavaScrpt -暂无内容-待更新

  2. Python之路(第四十六篇)多种方法实现python线程池(threadpool模块\multiprocessing.dummy模块\concurrent.futures模块)

    一.线程池 很久(python2.6)之前python没有官方的线程池模块,只有第三方的threadpool模块, 之后再python2.6加入了multiprocessing.dummy 作为可以使 ...

  3. Python之路【第十二篇】:Python面向对象高级

    一.反射 1 什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问.检测和修改它本身状态或行为的一种能力(自省).这一概念的提出很快引发了计算机科学领域关于应用反射性的研究 ...

  4. Python之路(第四十五篇)线程Event事件、 条件Condition、定时器Timer、线程queue

    一.事件Event Event(事件):事件处理的机制:全局定义了一个内置标志Flag,如果Flag值为 False,那么当程序执行 event.wait方法时就会阻塞,如果Flag值为True,那么 ...

  5. 【Python之路】第二十二篇--Django【基础篇】

    1 Django流程介绍 MTV模式       著名的MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层:他们之间以一种插件似的,松耦合的方式连接在一起. 模型负责业 ...

  6. Python之路【第二十二篇】:Django之Model操作

    Django之Model操作   一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bi ...

  7. 【Python之路】第十二篇--JavaScript

    JavaScript 历史 1992年Nombas开发出C-minus-minus(C--)的嵌入式脚本语言(最初绑定在CEnvi软件中).后将其改名ScriptEase.(客户端执行的语言) Net ...

  8. Python之路【第二十二篇】CMDB项目

    浅谈ITIL TIL即IT基础架构库(Information Technology Infrastructure Library, ITIL,信息技术基础架构库)由英国政府部门CCTA(Central ...

  9. Python之路【第十二篇续】jQuery案例详解

    jQuery 1.jQuery和JS和HTML的关系 首先了HTML是实际展示在用户面前的用户可以直接体验到的,JS是操作HTML的他能改变HTML实际展示给用户的效果! 首先了解JS是一门语言,他是 ...

随机推荐

  1. 请用js写一个函数,实现获取浏览器url中查询字符串中的参数并返回一个数组

    <script> console.log(getUrlArr()); function getUrlArr() { var arr = []; var url = "http:/ ...

  2. git: hook 修改提交信息

    git获取数字顺序版本号 因为git的版本使用的是hash值,不能很直观的看出那个版本,所以想找到一种方法,获取顺序的版本号,在网上找到了方法,可以获取顺序版本号 摘自:[使用bash从SVN和Git ...

  3. SCDM导入点数据

    我们有时候需要把外部的点导入SCDM当中,但是SCDM没有像ICEM或者DM那样直接提供点导入的选项,是不是SCDM就无法导入点的数据了呢?答案当然是否定的.把点导入SCDM中的方法总结如下(示例数据 ...

  4. libevent笔记3:evbuffer

    evbuffer 之前提到bufferevent结构体提供两个缓存区用来为读写提供缓存,并自动进行IO操作.这两个缓存区是使用Libevent中的evbuffer实现的,同样,Libevent中也提供 ...

  5. 如何使用 Django中的 get_queryset, get_context_data和 get_object 等方法

    原文: https://blog.csdn.net/HH2030/article/details/80994274

  6. Maven 教程(10)— Maven依赖详解

    原文地址:https://blog.csdn.net/liupeifeng3514/article/details/79545022 1.何为依赖? 比如你是个男的,你要生孩子,呸呸呸…男的怎么生孩子 ...

  7. sql 语言--- DML,DDL,DQL,DCL,TCL,CCL

    结构化查询语言(Structured Query Language)简称SQL                是一种特殊目的的编程语言,是一种数据库查询和程序设计语言,用于存取数据以及查询.更新和管理 ...

  8. js2048小游戏

    js2048小游戏,方格是怎么合并和移动的 index.html <html> <head> <meta charset="utf-8"> &l ...

  9. docker封装redis镜像

    一.概述 线上使用的redis版本为 3.2.13,但是dockerhub没有此版本的镜像.只有3.2.12但是默认的镜像启动时,是没有redis.conf的,如果需要加配置,需要自己定义配置文件. ...

  10. HDU校赛 | 2019 Multi-University Training Contest 6

    2019 Multi-University Training Contest 6 http://acm.hdu.edu.cn/contests/contest_show.php?cid=853 100 ...