Python并发:多线程与多进程的详解


Posted in Python onJanuary 24, 2019

本篇概要

1.线程与多线程

2.进程与多进程

3.多线程并发下载图片

4.多进程并发提高数字运算

关于并发

在计算机编程领域,并发编程是一个很常见的名词和功能了,其实并发这个理念,最初是源于铁路和电报的早期工作。比如在同一个铁路系统上如何安排多列火车,保证每列火车的运行都不会发生冲突。

后来在20世纪60年代,学术界对计算机的并行计算开始进行研究,再后来,操作系统能够进行并发的处理任务,编程语言能够为程序实现并发的功能。

线程与多线程

什么是线程

一个线程可以看成是一个有序的指令流(完成特定任务的指令),并且可以通过操作系统来调度这些指令流。

线程通常位于进程程里面,由一个程序计数器、一个堆栈和一组寄存器以及一个标识符组成。这些线程是处理器可以分配时间的最小执行单元。

线程之间是可以共享内存并且互相通信的。但是当两个线程之间开始共享内存,就无法保证线程执行的顺序,这可能导致程序错误,或者产生错误的结果。这个问题我们日后会专门提及。

下面这个图片展示了多个线程在多个CPU中的存在方式:

Python并发:多线程与多进程的详解

线程的类型

在一个典型的操作系统里面,一般会有两种类型的线程:

1.用户级线程:我们能够创建、运行和杀死的线程;

2.内核级线程:操作系统运行的低级别线程;

Python工作在用户级线程上,我们介绍的内容也主要是在用户级的线程上运行的。

什么是多线程

现在的CPU基本上都是多线程的CPU,比如我们随意从京东上找一个Inter的酷睿i5处理器,看看它的产品规格:

Python并发:多线程与多进程的详解

这些CPU能够同时运行多个线程来处理任务,其实从本质上来说,这些CPU是利用一个能够在多个线程之间快速切换的单个内核来完成多线程的运行的,切换线程的速度足够快,所以我们并不会感觉到。但实质上,它们并不是同时运行的。

为了形象的理解多线程,我们来回忆一个场景。

在大学时代,期末的时候,有些科目的老师为了不为难大家,把考试设为开卷考试,不知道大家面对开卷考试的时候,做题的顺序是怎样的?

在单线程的工作模式下,我们从选择题到填空题到简答题再到分析题,一个一个按顺序的写。

遇到一个特别难的题目,我们就要翻书翻资料了,当然既然是开卷考试,有些题目的答案就不可能直接出现在教科书中,那么我们就要花费更多的时间来找答案,直到考试结束,因为某个难题耗费的翻书时间太多,导致后面一些简单的题目也没用做,嗯,开卷都写不完试卷,挂科名额就给你了。

而在多线程的工作模式下,我们也是按顺序写,但是遇到难题时,我们会稍微从书中找找答案,如果没找到,就先做下面的题目,把会做的题目做好,做好了容易的题目,再回到那个难题上,仔细从书中的蛛丝马迹中找答案。

在这个例子里面,我们只是一个人来完成,如果想要更快地完成考试,就得跟其他同学通力合作和分工了。

让我们看看线程的一些优点:

1.多线程能够有效提升I/O阻塞型程序的效率;

2.与进程相比,占用的系统资源少;

3.线程间能够共享资源,方便进行通信;

线程还有一些缺点:

1.Python中有全局解释器锁(GIL)的限制;

2.虽然线程之间能够进行通信,但是容易导致程序结果出错,使用的时候必须小心;

3.在多线程之间切换的计算代价高,会导致程序的整体性能下降。

进程与多进程

进程在本质上与线程非常相似,进程几乎可以完成线程能够完成的任何事情。

按照上面开卷考试的例子,如果我们和室友组成一个小团伙,那么我们就有四个CPU(4个人),四个人分别写和找不同的答案,这样考试的效率会提高很多。

一个进程里面,包含一个主线程,还可以生成很多子线程,每个线程都包含自己的寄存器组合堆栈。如果有需要的话,可以将它们组成多线程。

下面是单线程单进程和多线程单进程的示例:

Python并发:多线程与多进程的详解

进程的特性

一个进程通常包含以下的内容:

1.进程ID,进程组ID,用户ID,组ID

2.环境

3.工作目录

4.程序指令

5.寄存器

6.堆栈

7.文件描述

8.进程间通信工具

9.等等……

进程有以下优点:

1.更好地利用多核处理器;

2.在处理CPU密集型任务时比多线程要好;

3.可以通过多进程来避免全局解释器锁(GIL)的局限;

4.崩溃的进程不会导致整个程序的崩溃;

同时,还有以下缺点:

1.进程之间没有共享资源;

2.进程需要消耗更多的内存;

多进程

在Python中我们可以使用多线程或者多进程的方式来运行我们的代码以改进传统的单线程方式的性能。

在单核的CPU上可以使用多线程提高处理能力,但是在现在的计算机CPU中,多核处理器早已普及,为了有效的利用机器的资源,我们有必要使用多进程来发挥机器的价值。

一个CPU内核将任务分配给其他CPU:

Python并发:多线程与多进程的详解

通过Python的进程处理模块multiprocessing,我们可以有效的利用机器上所有的处理器,这有助于我们在处理CPU密集型任务时获得更高的性能。

使用multiprocessing模块,查看我们机器上的CPU核心数量:

Python并发:多线程与多进程的详解

结果返回一个数字,为CPU核心数。

多进程不仅能够提高我们的计算机的利用率,还能够避免全局解释器锁的限制,一个潜在的缺点是多进程间不能进行共享和通信(可以通过其他手段实现),但是这个缺点同时也使多进程更加容易使用和避免出现崩溃。

Python的局限性

在文章的前面,我们谈到了在Python中存在的全局解释器锁GIL的局限性。那GIL到底是个什么东西?

GIL本质上是一个互斥锁,它可以防止多个线程同时执行Python代码。 它是一个只能由一个线程保持的锁,如果你想要一个线程去执行代码,那么在它执行代码之前,首先必须获得这个锁。 这样做的一个好处是,当它被锁定的时候,没有别的进程可以同时运行代码,一定程度上避免了线程间的冲突:

Python并发:多线程与多进程的详解

上面这个图说明了多个线程如何被GIL阻塞。每个线程必须等待获取到GIL才能进行下一步的运行,然后再释放GIL。线程之间使用随机循环的方式,所以并不能控制和保证哪个线程会先得到GIL。

这样的设计似乎很反人类,而这也是很多人诟病Python的地方。但是,这个设计确实是保证的多线程之间的内存安全。

现在我们已经了解了线程和进程,以及Python的一些限制,现在是时候了解一下我们如何在应用程序中使用多线程多进程,以提高程序的速度。

并发文件下载

毫无疑问的,展现多线程优点的一个例子就是使用多线程来下载多个图片或者文件,由于I/O的阻塞性质,下载任务可能是多线程最佳的运用场景了。

http://tool.bitefu.net/jiari/data/2017.txt是一个提供2017年所有节假日的文本文件:

Python并发:多线程与多进程的详解

我们访问10次,获得10次文本文件,然后保存在本地。

先看看一个普通的爬取:

Python并发:多线程与多进程的详解

我们引入了模块urllib.request,然后创建了一个函数downloadImage()用于下载文件,创建了一个函数main()用于对下载函数进行遍历20次。

Python并发:多线程与多进程的详解

耗时4秒多。

下面看看使用多线程的:

Python并发:多线程与多进程的详解

程序的前部分大同小异,后面我们创建了一个threads列表,,然后遍历10次,创建一个新的线程对象,将其添加到threads列表中,然后启动该线程。

最后,我们通过遍历我们的threads列表来调用我们的线程,然后调用join()方法在每个线程上,这确保我们在下载完文件之前,不会执行剩下的代码。

Python并发:多线程与多进程的详解

运行代码,可以发现程序几乎同时启动了10个下载任务,然后在图片下载完成后,再打印出来。

耗时0.1秒,效率提高很多。

但是需要注意的是,在网络中进行文件IO,还需要考虑网络状况和自身机器的影响,不同的网络状况下,完成的效率也不一样。

并发数字运算

I/O密集型的任务适合于多线程,而CPU密集型的任务则适合用多进程。

在下面的例子里,我们将找出100万个20000到100000000之间随机数的质数。

顺序运算:

Python并发:多线程与多进程的详解

Python并发:多线程与多进程的详解

耗时18秒。

多进程运算:

Python并发:多线程与多进程的详解

Python并发:多线程与多进程的详解

耗时11秒。

我们分别按顺序循环100万遍和使用多进程的进程池循环100万次,多进程模式下速度提升了近7秒。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对三水点靠木的支持。如果你想了解更多相关内容请查看下面相关链接

Python 相关文章推荐
Python实现Sqlite将字段当做索引进行查询的方法
Jul 21 Python
使用Python & Flask 实现RESTful Web API的实例
Sep 19 Python
Python基于socket实现简单的即时通讯功能示例
Jan 16 Python
python 获取list特定元素下标的实例讲解
Apr 09 Python
python实现从文件中读取数据并绘制成 x y 轴图形的方法
Oct 14 Python
浅谈python实现Google翻译PDF,解决换行的问题
Nov 28 Python
python如何实现异步调用函数执行
Jul 08 Python
Django  ORM 练习题及答案
Jul 19 Python
python基于socket进行端口转发实现后门隐藏的示例
Jul 25 Python
python实现将列表中各个值快速赋值给多个变量
Apr 02 Python
Python-typing: 类型标注与支持 Any类型详解
May 10 Python
Python与C++中梯度方向直方图的实现
Mar 17 Python
python用opencv批量截取图像指定区域的方法
Jan 24 #Python
python+pyqt5实现KFC点餐收银系统
Jan 24 #Python
Python微医挂号网医生数据抓取
Jan 24 #Python
Python实现查找二叉搜索树第k大的节点功能示例
Jan 24 #Python
几行Python代码爬取3000+上市公司的信息
Jan 24 #Python
python安装pywin32clipboard的操作方法
Jan 24 #Python
Python中extend和append的区别讲解
Jan 24 #Python
You might like
收音机指标测试方法及仪器
2021/03/01 无线电
php动态实现表格跨行跨列实现代码
2012/11/06 PHP
php获取服务器操作系统相关信息的方法
2016/10/08 PHP
php使用高斯算法实现图片的模糊处理功能示例
2016/11/11 PHP
网页里控制图片大小的相关代码
2006/06/25 Javascript
javascript 中对象的继承〔转贴〕
2007/01/22 Javascript
JS window.opener返回父页面的应用
2009/10/24 Javascript
juqery 学习之三 选择器 简单 内容
2010/11/25 Javascript
jquery实现输入框动态增减的实例代码
2013/07/14 Javascript
javascript通过navigator.userAgent识别各种浏览器
2013/10/25 Javascript
JavaScript 事件对象介绍
2015/04/13 Javascript
JS组件Form表单验证神器BootstrapValidator
2016/01/26 Javascript
Vue.js组件tabs实现选项卡切换效果
2016/12/01 Javascript
原生js实现倒计时--2018
2017/02/21 Javascript
js绑定事件和解绑事件
2017/04/27 Javascript
使用命令行工具npm新创建一个vue项目的方法
2017/12/27 Javascript
element-ui upload组件多文件上传的示例代码
2018/10/17 Javascript
JavaScript根据json生成html表格的示例代码
2018/10/24 Javascript
实现elementUI表单的全局验证的方法步骤
2019/04/29 Javascript
Vue使用NProgress的操作过程解析
2019/10/10 Javascript
vue element自定义表单验证请求后端接口验证
2019/12/11 Javascript
[03:11]TI9战队档案 - Alliance
2019/08/20 DOTA
python 开发的三种运行模式详细介绍
2017/01/18 Python
Python实现的拉格朗日插值法示例
2019/01/08 Python
python广度优先搜索得到两点间最短路径
2019/01/17 Python
Django 在iframe里跳转顶层url的例子
2019/08/21 Python
浅析Python打包时包含静态文件处理方法
2021/01/15 Python
Python爬虫入门教程02之笔趣阁小说爬取
2021/01/24 Python
新东方旗下远程教育网站:新东方在线
2020/03/19 全球购物
程序运行正确, 但退出时却"core dump"了,怎么回事
2014/02/19 面试题
社区助残日活动总结
2014/08/29 职场文书
收入证明范本
2015/06/12 职场文书
教师节简报
2015/07/20 职场文书
2016年优秀团员事迹材料
2016/02/25 职场文书
详解前端任务构建利器Gulp.js使用指南
2021/04/30 Javascript
Python中json.dumps()函数的使用解析
2021/05/17 Python