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 self,cls,decorator的理解
Jul 13 Python
Python random模块(获取随机数)常用方法和使用例子
May 13 Python
python基础教程之Hello World!
Aug 29 Python
python使用socket向客户端发送数据的方法
Apr 29 Python
python爬虫之模拟登陆csdn的实例代码
May 18 Python
让代码变得更易维护的7个Python库
Oct 09 Python
PyQt5实现从主窗口打开子窗口的方法
Jun 19 Python
python爬虫 Pyppeteer使用方法解析
Sep 28 Python
Python+OpenCV实现将图像转换为二进制格式
Jan 09 Python
TensorFlow实现指数衰减学习率的方法
Feb 05 Python
Python生成器实现简单"生产者消费者"模型代码实例
Mar 27 Python
基于python3.7利用Motor来异步读写Mongodb提高效率(推荐)
Apr 29 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 Squid中可缓存的动态网页设计
2008/09/17 PHP
基于AppServ,XAMPP,WAMP配置php.ini去掉警告信息(NOTICE)的方法详解
2013/05/07 PHP
PHP实现的超长文本分页显示功能示例
2018/06/04 PHP
Centos7.7 64位利用本地完整安装包安装lnmp/lamp套件教程
2021/03/09 Servers
让图片旋转任意角度及JQuery插件使用介绍
2013/03/20 Javascript
js中substr,substring,indexOf,lastIndexOf的用法小结
2013/12/27 Javascript
查找页面中所有类为test的结点的方法
2014/03/28 Javascript
运行Node.js的IIS扩展iisnode安装配置笔记
2015/03/02 Javascript
js制作支付倒计时页面
2016/10/21 Javascript
JS中substring与substr的用法
2016/11/16 Javascript
JavaScript实现倒计时跳转页面功能【实用】
2016/12/13 Javascript
JS实现仿饿了么在浏览器标签页失去焦点时网页Title改变
2017/06/01 Javascript
vuejs+element-ui+laravel5.4上传文件的示例代码
2017/08/12 Javascript
微信小程序实现工作时间段选择
2019/02/15 Javascript
JS定义函数的几种常用方法小结
2019/05/23 Javascript
JavaScript实现猜数字游戏
2020/05/20 Javascript
解决vue props传Array/Object类型值,子组件报错的情况
2020/11/07 Javascript
[44:21]Ti4 循环赛第四日 附加赛NEWBEE vs LGD
2014/07/13 DOTA
[02:20]DOTA2亚洲邀请赛 EHOME战队出场宣传片
2015/02/07 DOTA
python集合用法实例分析
2015/05/30 Python
一篇文章入门Python生态系统(Python新手入门指导)
2015/12/11 Python
python pandas读取csv后,获取列标签的方法
2018/11/12 Python
对python打乱数据集中X,y标签对的方法详解
2018/12/14 Python
通过python的matplotlib包将Tensorflow数据进行可视化的方法
2019/01/09 Python
python 循环数据赋值实例
2019/12/02 Python
详解基于Facecognition+Opencv快速搭建人脸识别及跟踪应用
2021/01/21 Python
TUMI澳大利亚网站:美国旅行箱包品牌
2017/03/27 全球购物
Guess欧洲官网:美国服饰品牌
2019/08/06 全球购物
瑞典香水、须后水和美容产品购物网站:Parfym-Klick.se
2019/12/29 全球购物
如果NULL定义成#define NULL((char *)0)难道不就可以向函数传入不加转换的NULL了吗
2012/02/15 面试题
幼儿园优秀教师事迹
2014/02/13 职场文书
婚庆司仪主持词
2014/03/15 职场文书
司法局2014法制宣传日活动总结
2014/11/01 职场文书
综合实践活动报告
2015/02/05 职场文书
2015年食品安全宣传周活动总结
2015/07/09 职场文书
Python入门之使用pandas分析excel数据
2021/05/12 Python