摘录 python核心编程

上节介绍的thread模块,是不支持守护线程的。当主线程退出的时候,所有的子线程都将终止,不管他们是否仍在工作。

本节开始,我们开始介绍python的另外多线程模块threading,该模块支持守护线程,其工作方式:守护线程一般是一个等待客户端请求的服务器。如果没有客户端请求,守护线程就是空闲的。如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出时不需要等待这个线程执行完成。

如果主线程准备退出的时候,不需要等待某些子线程完成,就可以为这些子线程设置守护线程标记。该标记值为真的时候,标示线程是不重要的,或者说该线程只是用来等待客户端请求而不做其他任何事情。

使用下面的语句:thread.daemon=True 可以将一个线程设置为守护线程。同样的也可以通过这个值来查看线程的守护状态。一个新的子线程会继承父线程的守护标记。整个python程序(也可以称作主线程)将在所有的非守护线程退出之后才退出。

threading模块除了Thread类之外,还包括许多好用的同步机制:

对象 描述
Thread 表示一个执行线程的对象
Lock 锁对象
RLock 可重入锁对象,使单一线程可以(再次)获得已持有的锁(递归锁)
Condition 条件变量对象,使得一个线程等待另外一个线程满足特定的条件,比如改变状态或者某个数据值
Event  条件变量的通用版本,任意数量的线程等待某个事件的发生,在该事件发生后所有的线程都将被激活
Semaphore 为线程间的有限资源提供一个计数器,如果没有可用资源时会被阻塞
BoundedSemaphore 于Semaphore相似,不过它不允许超过初始值
Timer 于Thread类似,不过它要在运行前等待一定时间
Barrier 创建一个障碍,必须达到指定数量的线程后才可以继续

其中,Thread类是threading模块的主要执行对象。

下面是Thread类的属性和方法列表:

属性 描述
Thread类属性
name 线程名
ident 线程的标识符
daemon 布尔值,表示这个线程是否是守护线程
Thread类方法
__init__(group=None,target=None,name=None,args=(),kwargs={},verbose=None,daemon=None) 实例化一个线程对象,需要一个可调用的target对象,以及参数args或者kwargs。还可以传递name和group参数。daemon的值将会设定thread.daemon的属性
start() 开始执行该线程
run() 定义线程的方法。(通常开发者应该在子类中重写)
join(timeout=None) 直至启动的线程终止之前一直挂起;除非给出了timeout(单位秒),否则一直被阻塞
getName() 返回线程名(该方法已被弃用)
setName() 设定线程名(该方法已弃用)
isAlive 布尔值,表示这个线程是否还存活(驼峰式命名,python2.6版本开始已被取代)
isDaemon() 布尔值,表示是否是守护线程(已经弃用)
setDaemon(布尔值) 在线程start()之前调用,把线程的守护标识设定为指定的布尔值(已弃用)

使用Thread类,可以有多种方法创建线程:

  • 创建Thread类的实例,传递一个函数
  • 创建Thread类的实例,传递一个可调用的类实例
  • 派生Thread类的子类,并创建子类的实例

一般的,我们会采用第一种或者第三种方法。如果需要一个更加符合面向对象的接口时,倾向于选择第三种方法,否则就用第一种方法吧。

第一种方法:创建Thread类,传递一个函数

下面的脚本中,我们先实例化Thread类,并传递一个函数(及其参数),当线程执行的时候,函数也会被执行:

  1. #!/usr/bin/env/ python
  2.  
  3. import threading
  4. from time import sleep,ctime
  5. #不再把4秒和2秒硬性的编码到不同的函数中,而是使用唯一的loop()函数,并把这些常量放进列表loops中
  6. loops=[4,2]
  7.  
  8. def loop(nloop,nsec):
  9. print('开始循环',nloop,'at:',ctime())
  10. sleep(nsec)
  11. print('循环',nloop,'结束于:',ctime())
  12.  
  13. def main():
  14. print('程序开始于:',ctime())
  15. threads=[]
  16. nloops=range(len(loops))
  17.  
  18. for i in nloops:
  19. t=threading.Thread(target=loop,args=(i,loops[i])) #循环 实例化2个Thread类,传递函数及其参数,并将线程对象放入一个列表中
  20. threads.append(t)
  21.  
  22. for i in nloops:
  23. threads[i].start() #循环 开始线程
  24.  
  25. for i in nloops:
  26. threads[i].join() #循环 join()方法可以让主线程等待所有的线程都执行完毕。
  27.  
  28. print('任务完成于:',ctime())
  29.  
  30. if __name__=='__main__':
  31. main()

执行结果:

  1. PS C:\Users\WC> python E:\Python3.6.3\workspace\mtsleepC.py
  2. 程序开始于: Thu Mar 29 21:35:13 2018
  3. 开始循环 0 at: Thu Mar 29 21:35:13 2018
  4. 开始循环 1 at: Thu Mar 29 21:35:13 2018
  5. 循环 1 结束于: Thu Mar 29 21:35:15 2018
  6. 循环 0 结束于: Thu Mar 29 21:35:17 2018
  7. 任务完成于: Thu Mar 29 21:35:17 2018

和上节的thread模块相比,不同点在于:实现同样的效果,thread模块需要锁对象,而threading模块的Thread类不需要。实例化Thread(调用Thread())和调用thread.start_new_thread()的最大区别就是新线程不会立即执行!这是一个非常有用的同步功能,尤其在我们不希望线程立即开始执行的时候。

  当所有的线程都分配完成之后,通过调用每个线程的start()方法再让他们开始。相比于thread模块的管理一组锁(分配、获取、释放检查锁状态)来说,threading模块的Thread类只需要为每个线程调用join()方法即可。join(timeout=None)方法将等待线程结束,或者是达到指定的timeout时间时。这种锁又称为自旋锁。

  最重要的是:join()方法,其实你根本不需要调用它。一旦线程启动,就会一直执行,直到给定的函数完成后退出。如果主线程还有其他事情要做(并不需要等待这些线程完成),可以不调用join()。join()只有在你需要等待线程完成时候才是有用的。

例如上面的脚本中,我们注释掉join()代码:

  1. ……
  2. for i in nloops:
  3. threads[i].start() #循环 开始线程
  4. '''
  5. for i in nloops:
  6. threads[i].join() #循环 join()方法可以让主线程等待所有的线程都执行完毕。
  7. '''
  8. print('任务完成于:',ctime())
  9.  
  10. if __name__=='__main__':
  11. main()

运行结果:

  1. PS C:\Users\WC> python E:\Python3.6.3\workspace\mtsleepC.py
  2. 程序开始于: Thu Mar 29 21:55:10 2018
  3. 开始循环 0 at: Thu Mar 29 21:55:10 2018
  4. 开始循环 1 at: Thu Mar 29 21:55:10 2018
  5. 任务完成于: Thu Mar 29 21:55:10 2018
  6. 循环 1 结束于: Thu Mar 29 21:55:12 2018
  7. 循环 0 结束于: Thu Mar 29 21:55:14 2018

我们发现:主线程的任务比两个循环线程要先执行(任务完成于……在 循环X结束……的前面)

第二种方法:创建Thread类的实例,传递一个可调用的类实例

创建线程时,于传入函数类似的方法是传入一个可调用的类的实例,用于线程执行——这种方法更加接近面向对象的多线程编程。比起一个函数或者从一个函数组中选择而言,这种可调用的类包含一个执行环境,有更好的灵活性。

在上述的mtsleepC.py脚本中添加一个新类ThreadFunc,稍微改动一番,形成mtsleepD.py文件:

  1. #!/usr/bin/env python
  2.  
  3. import threading
  4. from time import sleep,ctime
  5.  
  6. loops=[4,2]
  7.  
  8. class ThreadFunc(object):
  9. def __init__(self,func,args,name=''):
  10. self.name=name
  11. self.func = func
  12. self.args=args
  13.  
  14. def __call__(self):
  15. self.func(*self.args)
  16.  
  17. def loop(nloop,nsec):
  18. print('开始循环',nloop,'在:',ctime())
  19. sleep(nsec)
  20. print('结束循环',nloop,'于:',ctime())
  21.  
  22. def main():
  23. print('程序开始于:',ctime())
  24. threads = []
  25. nloops = range(len(loops))
  26.  
  27. for i in nloops:
  28. t = threading.Thread(target=ThreadFunc(loop,(i,loops[i]),loop.__name__)) #传递一个可调用类的实例
  29. threads.append(t)
  30.  
  31. for i in nloops:
  32. threads[i].start() #开始所有的线程
  33.  
  34. for i in nloops:
  35. threads[i].join() #等待所有的线程执行完毕
  36.  
  37. print('任务完成于:',ctime())
  38.  
  39. if __name__=='__main__':
  40. main()

执行结果:

  1. PS C:\Users\WC> python E:\Python3.6.3\workspace\mtsleepD.py
  2. 程序开始于: Thu Mar 29 22:30:02 2018
  3. 开始循环 0 在: Thu Mar 29 22:30:02 2018
  4. 开始循环 1 在: Thu Mar 29 22:30:02 2018
  5. 结束循环 1 于: Thu Mar 29 22:30:04 2018
  6. 结束循环 0 于: Thu Mar 29 22:30:06 2018
  7. 任务完成于: Thu Mar 29 22:30:06 2018

上述脚本中,主要添加了ThreadFunc类,并在实例化Thread对象时,通过传参的形式同时实例化了可调用类ThreadFunc。这里同时完成了两个实例化。

我们研究一下创建ThreadFunc类的思想:我们希望这个类更加通用,而不是局限于loop()函数,为此,添加了一些新的东西,比如这个类保存了函数自身、函数的参数、以及函数名。构造函数__init__()用于设定上述值。当创建新线程的时候,Thread类的代码将调用ThreadFunc对象,此时会调用__call__()这个特殊方法。

(老实说,这种方法显得有些尴尬,并且稍微难以阅读)

第三种方法:派生Thread的子类,并创建子类的实例

和方法二相比,方法三再创建线程时使用子类要相对更容易阅读,下面是mtsleepE.py脚本:

  1. #!/usr/bin/env pyhton
  2.  
  3. import threading
  4. from time import sleep,ctime
  5.  
  6. loops=[4,2]
  7.  
  8. class MyThread(threading.Thread):
  9. def __init__(self,func,args,name=''):
  10. threading.Thread.__init__(self)
  11. self.name = name
  12. self.func = func
  13. self.args = args
  14.  
  15. def run(self):
  16. self.func(*self.args)
  17.  
  18. def loop(nloop,nsec):
  19. print('开始循环',nloop,'在:',ctime())
  20. sleep(nsec)
  21. print('结束循环',nloop,'于:',ctime())
  22.  
  23. def main():
  24. print('程序开始于:',ctime())
  25. threads = []
  26. nloops = range(len(loops))
  27.  
  28. for i in nloops:
  29. t = MyThread(loop,(i,loops[i]),loop.__name__)
  30. threads.append(t)
  31.  
  32. for i in nloops:
  33. threads[i].start()
  34.  
  35. for i in nloops:
  36. threads[i].join()
  37.  
  38. print('所有的任务完成于:',ctime())
  39.  
  40. if __name__ =='__main__':
  41. main()

运行结果:

  1. PS C:\Users\WC> python E:\Python3.6.3\workspace\mtsleepE.py
  2. 程序开始于: Thu Mar 29 22:52:18 2018
  3. 开始循环 0 在: Thu Mar 29 22:52:18 2018
  4. 开始循环 1 在: Thu Mar 29 22:52:18 2018
  5. 结束循环 1 于: Thu Mar 29 22:52:20 2018
  6. 结束循环 0 于: Thu Mar 29 22:52:22 2018
  7. 所有的任务完成于: Thu Mar 29 22:52:22 2018

比较方法二和方法三,重要的变化在于:MyThread子类的构造函数必须先调用其父类的构造函数;重写run()方法,代替方法二中的__call__()方法。

优化第三种方法:

对MyThread类进行修改,增加一些调试信息的输出,并将该类单独存储为myThread.py的模块(MyThread.py),以便在以后的例子中需要的时候导入这个类。另外,除了简单的调用函数外,还可以将结果保存在实例的属性self.res中,同时增加新的方法getResult()来获取这个值:

  1. #!/usr/bin/env python
  2.  
  3. import threading
  4. from time import ctime
  5.  
  6. class MyThread(threading.Thread):
  7. def __init__(self,func,args,name=''):
  8. threading.Thread.__init__(self)
  9. self.func = func
  10. self.name = name
  11. self.args = args
  12.  
  13. def run(self):
  14. print('开始执行',self.name,' 在:',ctime())
  15. self.res = self.func(*self.args)
  16. print(self.name,'结束于:',ctime())
  17.  
  18. def getResult(self):
  19. return self.res

下面,我们介绍多线程和单线程执行效果对比的时候,会用到这个MyThread.py模块。

脚本mtfacfib.pt,比较了递归求斐波那契、阶乘和累计函数的执行,分别按照单线程和多线程的方式执行同样的任务:

  1. #!/usr/bin/env python
  2.  
  3. from myThread import MyThread
  4. from time import ctime,sleep
  5. #斐波那契
  6. def fib(x):
  7. sleep(0.005)
  8. if x < 2:
  9. return 1
  10. return fib(x-1)+fib(x-2)
  11. #阶乘
  12. def fac(x):
  13. sleep(0.1)
  14. if x < 2:
  15. return 1
  16. return x*fac(x-1)
  17. #累加
  18. def sum(x):
  19. sleep(0.1)
  20. if x < 2 :
  21. return 1
  22. return x + sum(x-1)
  23.  
  24. funcs=[fib,fac,sum]
  25. n = 12
  26.  
  27. def main():
  28. nfuncs = range(len(funcs))
  29.  
  30. #单线程
  31. print('单线程模式')
  32. for i in nfuncs:
  33. print('开始',funcs[i].__name__,' 在:',ctime())
  34. print(funcs[i](n))
  35. print(funcs[i].__name__,'结束于:',ctime())
  36.  
  37. #多线程
  38. print('多线程模式')
  39. threads = []
  40. for i in nfuncs :
  41. t = MyThread(funcs[i],(n,),funcs[i].__name__)
  42. threads.append(t)
  43.  
  44. for i in nfuncs:
  45. threads[i].start()
  46.  
  47. for i in nfuncs:
  48. threads[i].join()
  49. print(threads[i].getResult())
  50.  
  51. print('所有的任务结束')
  52.  
  53. if __name__ == '__main__':
  54. main()

运行结果:

  1. PS C:\Users\WC> python E:\Python3.6.3\workspace\mtfacfib.py
  2. 单线程模式
  3. 开始 fib 在: Fri Mar 30 14:08:43 2018
  4. 233
  5. fib 结束于: Fri Mar 30 14:08:45 2018
  6. 开始 fac 在: Fri Mar 30 14:08:45 2018
  7. 479001600
  8. fac 结束于: Fri Mar 30 14:08:46 2018
  9. 开始 sum 在: Fri Mar 30 14:08:46 2018
  10. 78
  11. sum 结束于: Fri Mar 30 14:08:48 2018
  12. 多线程模式
  13. 开始执行 fib 在: Fri Mar 30 14:08:48 2018
  14. 开始执行 fac 在: Fri Mar 30 14:08:48 2018
  15. 开始执行 sum 在: Fri Mar 30 14:08:48 2018
  16. fac 结束于: Fri Mar 30 14:08:49 2018
  17. sum 结束于: Fri Mar 30 14:08:49 2018
  18. fib 结束于: Fri Mar 30 14:08:50 2018
  19. 233
  20. 479001600
  21. 78
  22. 所有的任务结束

程序中,为了看到多线程如何改善性能的,我们加入了sleep函数用于减慢执行速度。

看到单线程模式中,只是简单的一次调用每个函数,并在函数结束执行的时候立即显示相关的结果;而使用多线程的时候,并不会立刻显示结果,因为我们希望MyThread类越通用越好(有输出和无输出都能执行),我们一直等到所有线程都join之后,再调用getResult()方法显示每个函数的返回值。

python 多线程编程之threading模块(Thread类)创建线程的三种方法的更多相关文章

  1. 《Java多线程面试题》系列-创建线程的三种方法及其区别

    1. 创建线程的三种方法及其区别 1.1 继承Thread类 首先,定义Thread类的子类并重写run()方法: package com.zwwhnly.springbootaction.javab ...

  2. python 多线程编程之_thread模块

    参考书籍:python核心编程 _thread模块除了可以派生线程外,还提供了基本的同步数据结构,又称为锁对象(lock object,也叫原语锁.简单锁.互斥锁.互斥和二进制信号量). 下面是常用的 ...

  3. java 创建线程的三种方法Callable,Runnable,Thread比较及用法

    转自:http://www.chinaitlab.com/Java/line/942440.html 编写多线程程序是为了实现多任务的并发执行,从而能够更好地与用户交互.一般有三种方法,Thread, ...

  4. Java多线程之创建线程的三种方式比较

    转载请注明原文地址:http://www.cnblogs.com/ygj0930/p/6560057.html  一:继承Thread类创建线程 1:继承Thread类定义线程子类: 2:重写run( ...

  5. python并发编程之threading线程(一)

    进程是系统进行资源分配最小单元,线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.进程在执行过程中拥有独立的内存单元,而多个线程共享内存等资源. 系列文章 py ...

  6. Android(java)学习笔记62:继承Thread类创建线程类

    package cn.itcast_02; /* * 该类要重写run()方法,为什么呢? * 不是类中的所有代码都需要被线程执行的. * 而这个时候,为了区分哪些代码能够被线程执行,java提供了T ...

  7. 用Thread类创建线程-2

    支持原创,本系列文章均转自:http://www.blogjava.net/nokiaguy/category/38172.html 在Java中创建线程有两种方法:使用Thread类和使用Runna ...

  8. 用Thread类创建线程

    在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程, ...

  9. Android(java)学习笔记2:继承Thread类创建线程类

    1. 继承Thread类 创建线程类: package cn.itcast_02; /* * 该类要重写run()方法,为什么呢? * 不是类中的所有代码都需要被线程执行的. * 而这个时候,为了区分 ...

随机推荐

  1. 痞子衡嵌入式:恩智浦机器视觉模块OpenMV-RT那些事(1)- 初体验

    大家好,我是痞子衡,是正经搞技术的痞子.本系列痞子衡给大家介绍的是机器视觉模块OpenMV-RT初体验. 近些年机器视觉应用一直是个很火的方向,想象一下机器如果能长上"眼睛",是不 ...

  2. 基于 HTML5 + WebGL 的地铁 3D 可视化系统

    前言 工业互联网,物联网,可视化等名词在我们现在信息化的大背景下已经是耳熟能详,日常生活的交通,出行,吃穿等可能都可以用信息化的方式来为我们表达,在传统的可视化监控领域,一般都是基于 Web SCAD ...

  3. [ch04-03] 用神经网络解决线性回归问题

    系列博客,原文在笔者所维护的github上:https://aka.ms/beginnerAI, 点击star加星不要吝啬,星越多笔者越努力. 4.3 神经网络法 在梯度下降法中,我们简单讲述了一下神 ...

  4. 《手把手教你》系列练习篇之7-python+ selenium自动化测试 -压轴篇(详细教程)

    1. 简介 “压轴”原本是戏曲名词,指一场折子戏演出的倒数第二个剧目.在现代社会中有很多应用,比如“压轴戏”,但压轴也是人们知识的一个盲区.“压轴”本意是指倒数第二个节目,而不是人们常说的倒数第一个, ...

  5. windows下python IDE安装注意事项&Python安装及编辑器UliPad安装

    python下载地址: http://www.python.org/download/releases/2.7.6/ 我自己用的是ulipad  ,但是注意  ulipad和python的版本一定要配 ...

  6. 小白的springboot之路(六)、跨域解决方案CORS

    0-前言 前后端分离.分布式集群,经常都会涉及到跨域访问,而浏览器基于同源策略,正常情况下是不能跨域的,这就需要我们解决跨域访问问题:spring boot解决跨域也比较简单: 1-CORS跨域解决方 ...

  7. Too many open files的四种解决办法

    [摘要] Too many open files有四种可能:一 单个进程打开文件句柄数过多,二 操作系统打开的文件句柄数过多,三 systemd对该进程进行了限制,四 inotify达到上限. 领导见 ...

  8. window安装jboss服务器

    window安装jboss服务器 1.下载jboss服务器 地址:http://download.jboss.org/jbossas/7.1/jboss-as-7.1.1.Final/jboss-as ...

  9. React Native--Animated:`useNativeDriver`is not supported because the native animated module is missing

    目录 问题描述 解决方法 问题描述 react-native init 项目,在运行中报错 解决方法 打开Xcode项目,点击Libraries文件夹右键Add Files to "项目名& ...

  10. CSS_实现京东购物车静态页面

    主页面分配: <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> < ...