Python用threading实现多线程详解


Posted in Python onFebruary 03, 2017

多线程

多线程是个提高程序运行效率的好办法,本来要顺序执行的程序现在可以并行执行,可想而知效率要提高很多。但是多线程也不是能提高所有程序的效率。程序的两个极端是‘CPU 密集型'和‘I/O 密集型'两种,多线程技术比较适用于后者,因为在串行结构中当你去读写磁盘或者网络通信的时候 CPU 是闲着的,毕竟网络比磁盘要慢几个数量级,磁盘比内存慢几个数量级,内存又比 CPU 慢几个数量级。多线程技术就可以同时执行,比如你的程序需要发送 N 个 http 数据包(10 秒),还需要将文件从一个位置复制到另一个位置(20 秒),然后还需要统计另一个文件中'hello,world'字符串的出现次数(4 秒),现在一共是要用 34 秒。但是因为这些操作之间没有关联,所以可以写成多线程程序,几乎只需要 20 秒就完成了。这是针对 I/O 密集型的,如果是 CPU 密集型的就不行了。比如我的程序要计算 1000 的阶乘(10 秒),还要计算 100000 的累加(5 秒),那么即使程序是并行的,还是会要用 15 秒,甚至更多。因为当程序使用 CPU 的时候 CPU 是通过轮转来执行的,IO 密集型的程序可以在 IO 的同时用 CPU 计算,但是这里的 CPU 密集型就只能先执行一会儿线程 1 再执行一会儿线程 2。所以就需要 15 秒,甚至会更多,因为 CPU 在切换的时候需要耗时。解决 CPU 密集型程序的多线程问题就是 CPU 的事情了,比如 Intel 的超线程技术,可以在同一个核心上真正的并行两个线程,所以称之为‘双核四线程'或者‘四核八线程',我们这里具体的先不谈,谈我也不知道。

Python 骗人

说了这么多多线程的好处,但是其实 Python 不支持真正意义上的多线程编程。在 Python 中有一个叫做 GIL 的东西,中文是 全局解释器 ,这东西控制了 Python,让 Python 只能同时运行一个线程。相当于说真正意义上的多线程是由 CPU 来控制的,Python 中的多线程由 GIL 控制。如果有一个 CPU 密集型程序,用 C 语言写的,运行在一个四核处理器上,采用多线程技术的话最多可以获得 4 倍的效率提升,但是如果用 Python 写的话并不会有提高,甚至会变慢,因为线程切换的问题。所以 Python 多线程相对更加适合写 I/O 密集型程序,再说了真正的对效率要求很高的 CPU 密集型程序都用 C/C++ 去了。

第一个多线程

Python 中多线程的库一般用thread和threading这两个,thread不推荐新手和一般人使用,threading模块就相当够用了。

有一个程序,如下。两个循环,分别休眠 3 秒和 5 秒,串行执行的话需要 8 秒。

#!/usr/bin/env python
# coding=utf-8
import time
def sleep_3():
 time.sleep(3)
def sleep_5():
 time.sleep(5)
if __name__ == '__main__':
 start_time = time.time()
 print 'start sleep 3'
 sleep_3()
 print 'start sleep 5'
 sleep_5()
 end_time = time.time()
 print str(end_time - start_time) + ' s'

输出是这样的

start sleep 3
start sleep 5
8.00100016594 s

然后我们对它进行修改,使其变成多线程程序,虽然改动没有几行。首先引入了 threading 的库,然后实例化一个 threading.Thread 对象,将一个函数传进构造方法就行了。然后调用 Thread 的 start 方法开始一个线程。join() 方法可以等待该线程结束,就像我下面用的,如果我不加那两个等待线程结束的代码,那么就会直接执行输出时间的语句,这样一来统计的时间就不对了。

#!/usr/bin/env python
# coding=utf-8
import time
import threading # 引入threading
def sleep_3():
 time.sleep(3)
def sleep_5():
 time.sleep(5)
if __name__ == '__main__':
 start_time = time.time()
 print 'start sleep 3'
 thread_1 = threading.Thread(target=sleep_3)  # 实例化一个线程对象,使线程执行这个函数
 thread_1.start()  # 启动这个线程
 print 'start sleep 5'
 thread_2 = threading.Thread(target=sleep_5)  # 实例化一个线程对象,使线程执行这个函数
 thread_2.start()  # 启动这个线程
 thread_1.join()  # 等待thread_1结束
 thread_2.join()  # 等待thread_2结束
 end_time = time.time()
 print str(end_time - start_time) + ' s'

执行结果是这样的

start sleep 3
start sleep 5
5.00099992752 s

daemon 守护线程

在我们理解中守护线程应该是很重要的,类比于 Linux 中的守护进程。但是在threading.Thread中偏偏不是。

如果把一个线程设置为守护线程,就表示这个线程是不重要的,进程退出的时候不需要等待这个线程执行完成。 ---------《Python 核心编程 第三版》

在 Thread 对象中默认所有线程都是非守护线程,这里有两个例子说明区别。这段代码执行的时候就没指定my_thread的daemon属性,所以默认为非守护,所以进程等待他结束。最后就可以看到 100 个 hello,world

#!/usr/bin/env python
# coding=utf-8
import threading
def hello_world():
 for i in range(100):
  print 'hello,world'
if __name__ == '__main__':
 my_thread = threading.Thread(target=hello_world)
 my_thread.start()

这里设置了my_thread为守护线程,所以进程直接就退出了,并没有等待他的结束,所以我们看不到 100 个 hello,world 只有几个而已。甚至还会抛出一个异常告诉我们有线程没结束。

#!/usr/bin/env python
# coding=utf-8
import threading
def hello_world():
 for i in range(100):
  print 'hello,world'
if __name__ == '__main__':
 my_thread = threading.Thread(target=hello_world)
 my_thread.daemon = True # 设置了标志位True
 my_thread.start()

传个参数

之前的代码都是直接执行一段代码,没有过参数的传递,那么怎么传递参数呢?其实还是很简单的。threading.Thread(target=hello_world, args=('hello,', 'world'))就可以了。args 后面跟的是一个元组,如果没有参数可以不写,如果有参数就直接在元组里按顺序添加就行了。

#!/usr/bin/env python
# coding=utf-8
import threading
def hello_world(str_1, str_2):
 for i in range(10):
  print str_1 + str_2
if __name__ == '__main__':
 my_thread = threading.Thread(target=hello_world, args=('hello,', 'world')) # 这里传递参数
 my_thread.start()

再来个多线程

threading 有三种创建 Thread 对象的方式,但是一般只会用到两种,一种是上面0X02说的传个函数进去,另一种就是这里说的继承threading.Thread。在这儿我们自己定义了两个类,类里重写了 run() 方法,也就是调用 start() 之后执行的代码,开启线程就和之前开启是一样的。之前的方式更面向过程,这个更面向对象。

#!/usr/bin/env python
# coding=utf-8
import threading
class MyThreadHello(threading.Thread):
 def run(self):
  for i in range(100):
   print 'hello'
class MyThreadWorld(threading.Thread):
 def run(self):
  for i in range(100):
   print 'world'
if __name__ == '__main__':
 thread_hello = MyThreadHello()
 thread_world = MyThreadWorld()
 thread_hello.start()
 thread_world.start()

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。

Python 相关文章推荐
Python深入学习之上下文管理器
Aug 31 Python
Python httplib模块使用实例
Apr 11 Python
Django的数据模型访问多对多键值的方法
Jul 21 Python
python使用psutil模块获取系统状态
Aug 27 Python
Python实现购物程序思路及代码
Jul 24 Python
python网络爬虫之如何伪装逃过反爬虫程序的方法
Nov 23 Python
python按比例随机切分数据的实现
Jul 11 Python
python实现LBP方法提取图像纹理特征实现分类的步骤
Jul 11 Python
Python自动化xpath实现自动抢票抢货
Sep 19 Python
Scrapy 配置动态代理IP的实现
Sep 28 Python
Ubuntu权限不足无法创建文件夹解决方案
Nov 14 Python
Django自带的用户验证系统实现
Dec 18 Python
win10环境下python3.5安装步骤图文教程
Feb 03 #Python
python strip() 函数和 split() 函数的详解及实例
Feb 03 #Python
利用python画一颗心的方法示例
Jan 31 #Python
利用Python脚本生成sitemap.xml的实现方法
Jan 31 #Python
利用python实现命令行有道词典的方法示例
Jan 31 #Python
Python爬虫包 BeautifulSoup  递归抓取实例详解
Jan 28 #Python
python 编程之twisted详解及简单实例
Jan 28 #Python
You might like
php实现删除指定目录下相关文件的方法
2014/10/20 PHP
ThinkPHP框架设计及扩展详解
2014/11/25 PHP
详解配置 Apache 服务器支持 PHP 文件的解析
2017/02/15 PHP
PHP用PDO如何封装简单易用的DB类详解
2017/07/30 PHP
yii2.0整合阿里云oss的示例代码
2017/09/19 PHP
Laravel中9个不经常用的小技巧汇总
2019/04/16 PHP
TNC vs BOOM BO3 第一场2.13
2021/03/10 DOTA
网页开发中的容易忽略的问题 javascript HTML中的table
2009/04/15 Javascript
javascript textContent与innerText的异同分析
2010/10/22 Javascript
EasyUI中的tree用法介绍
2011/11/01 Javascript
javascript中String对象的slice()方法分析
2014/12/20 Javascript
js实现拖拽效果(构造函数)
2015/12/14 Javascript
JavaScript操作选择对象的简单实例
2016/05/16 Javascript
JavaScript数组方法大全(推荐)
2016/07/05 Javascript
JavaScript中数组Array方法详解
2017/02/27 Javascript
javascript编程实现栈的方法详解【经典数据结构】
2017/04/11 Javascript
angular使用bootstrap方法手动启动的实例代码
2017/07/18 Javascript
Node.js+jade抓取博客所有文章生成静态html文件的实例
2017/09/19 Javascript
在 Node.js 中使用原生 ES 模块方法解析
2017/09/19 Javascript
Vue实现todo应用的示例
2021/02/20 Vue.js
python字符串排序方法
2014/08/29 Python
Python使用pylab库实现画线功能的方法详解
2017/06/08 Python
Python通过future处理并发问题
2017/10/17 Python
numpy.linspace 生成等差数组的方法
2018/07/02 Python
Python logging模块原理解析及应用
2020/08/13 Python
实例教程 纯CSS3打造非常炫的加载动画效果
2014/11/05 HTML / CSS
西班牙伏林航空公司:Vueling
2016/08/05 全球购物
swtich是否能作用在byte上,是否能作用在long上,是否能作用在String上?
2013/03/30 面试题
电子商务专业学生的自我鉴定
2013/11/28 职场文书
葬礼司仪主持词
2014/03/31 职场文书
部队反四风对照检查材料
2014/09/26 职场文书
县委常委班子专题民主生活会查摆问题及整改措施
2014/09/27 职场文书
2015教师年度考核评语
2015/03/25 职场文书
酒店工程部的岗位职责汇总大全
2019/10/23 职场文书
MySQL如何解决幻读问题
2021/08/07 MySQL
Rust 连接 PostgreSQL 数据库的详细过程
2022/01/22 PostgreSQL