深入理解Python 多线程


Posted in Python onJune 16, 2020

Python里的多线程是假的多线程,不管有多少核,同一时间只能在一个核中进行操作!利用Python的多线程,只是利用CPU上下文切换的优势,看上去像是并发,其实只是个单线程,所以说他是假的单线程。

那么什么时候用多线程呢?

首先要知道:

  • io操作不占用CPU
  • 计算操作占CPU,像2+5=5

Python的多线程不适合CPU密集操作型的任务,适合io密集操作型的任务,例如:SocketServer

如果现在再有CPU密集操作型的任务,那该怎么办呢?

首先说,多进程的进程之间是独立的,然后注意了,python的线程用的是系统的原生线程,python的进程也是用系统的原生进程,那原生进程是由操作系统维护的,说白了python只是利用C原生代码库的接口?E嚓起了个进程,真正的进程管理还是由操作系统来完成的,那么操作系统本身有GIL全局解释器锁吗?答案是没有的,且两个进程之间的数据是完全独立的,不能互相访问,所以不需要锁的概念,所以不存在GIL概念,所以在这种情况下,每个进程至少会有一个线程,如果现在我的操作系统是八核的,我起八个进程,然后每个进程里面都有一个线程,那么就相当于八线程了,八个线程跑在八核上,那么就相当于利用多核了,那么问题就解决了!

唯一的坏处是八个线程之间的数据是不能共享的,独立的!利用这种方法可以折中的解决多核运算的问题!

先看一段简单的多进程的程序:

import multiprocessing
import time

def run(name):
 time.sleep(2)
 print('hello', name)

if __name__ == '__main__':
 for i in range(10):
  p = multiprocessing.Process(target=run, args=('bob%s'%i,))
  p.start()

程序的执行结果为:

hello bob0
hello bob1
hello bob3
hello bob2
hello bob5
hello bob9
hello bob7
hello bob8
hello bob4
hello bob6

那么,如果我想取我的进程号,那该怎么取呢?

from multiprocessing import Process
import os

def info(title):
 print(title)
 print('module name:', __name__)
 print('parent process:', os.getppid()) # 父进程ID 
 print('process id:', os.getpid()) # 自己进程的ID
 print("\n\n")

def f(name):
 info('\033[31;1mfunction f\033[0m')
 print('hello', name)

if __name__ == '__main__':
 info('\033[32;1mmain process line\033[0m')
 p = Process(target=f, args=('bob',))
 p.start()
 p.join()

程序执行的结果为:

main process line
module name: __main__
parent process: 5252
process id: 6576

function f
module name: __mp_main__
parent process: 6576
process id: 2232

hello bob

深入理解Python 多线程

其实这幅图片的意思是,每一个子进程都是由他父进程启动的。

进程间通讯

我们说两个进程之间的内存之间是相互独立的,那么这两个进程能够进行通信吗?说A进程向访问B进程的数据,能访问吗?肯定是不可以访问的!但是,我就是想访问,也就是两个独立的内存想互相访问,那该怎么办呢?

有那么几种方式,但是呢!万变不离其宗,也即是说你必须找到一个中间件,有那么几种中间件,那么先来看看是哪几种

第一种Queues

使用方法跟threading里的queue差不多

from multiprocessing import Process, Queue

def f(q):
 q.put([42, None, 'hello'])

if __name__ == '__main__':
 q = Queue()
 p = Process(target=f, args=(q,))
 p.start()
 print(q.get()) # prints "[42, None, 'hello']"
 p.join()

我们看这两个进程,父进程的q是怎么传给子进程的?我们来讨论一下

现在我们是不是认为数据共享了,两个进程共享了一个q,其实不是的,其实是相当于克隆了一个q,然后在父进程里创建个子进程,也就是父进程把自己的q克隆了一份交给了子进程,子进程这个时候往这个q里面放了一份数据,父进程能够获取到 。那么这么说就不对了,那克隆了一个q,也就是两个q了,B往q里放了一个数据,那么与另一个q,也就是A的q也就没关系了,嗳,按说是这个样子的,但是实际上呢,它是不是想实现个数据的共享啊,就相当于把A这个q里的数据序列化了,序列化到了一个中间的位置,而中间位置有一个翻译,他把这个数据反序列化给A,放在了A的q里,那么也就是实现了所谓的数据共享了。

程序执行的结果为:

[42, None, 'hello']

第二种Pipes

Pipe()函数返回一个由管道连接的连接对象,默认情况下是双工(双向)。 例如:

from multiprocessing import Process, Pipe

def f(conn):
 conn.send("父亲,安好?") # 儿子发
 print("son receive:",conn.recv())
 conn.close()


if __name__ == '__main__':
 parent_conn, child_conn = Pipe()
 p = Process(target=f, args=(child_conn,))
 p.start()
 print("father receive:",parent_conn.recv()) # 父亲收
 parent_conn.send("儿子,安好?")
 p.join()

程序执行后的结果为:

father receive: 父亲,安好?
son receive: 儿子,安好?

Pipe()返回的两个连接对象代表管道的两端。 每个连接对象都有send()和recv()方法(以及其他方法)。 请注意,如果两个进程(或线程)同时尝试读取或写入管道的同一端,则管道中的数据可能会损坏。 当然,同时使用管道的不同端部的过程不存在损坏的风险。

第三种Managers

Manager()返回的管理器对象控制一个服务器进程,该进程保存Python对象并允许其他进程使用代理操作它们。

Manager()返回的管理器将支持类型列表,dict,Namespace,Lock,RLock,Semaphore,BoundedSemaphore,Condition,Event,Barrier,Queue,Value和Array。 例如,

from multiprocessing import Process, Manager
import os

def f(d, l):
 d[1] = '1'
 d['2'] = 2
 d[0.25] = None
 l.append(os.getpid())
 print(l)

if __name__ == '__main__':
 with Manager() as manager:
  d = manager.dict() # 用专门的语法生成一个可在多个进程之间进行传递和共享的一个字典

  l = manager.list(range(5)) # # 用专门的语法生成一个可在多个进程之间进行传递和共享的一个列表,默认里有5个数据
  p_list = []
  for i in range(10):
   p = Process(target=f, args=(d, l))
   p.start()
   p_list.append(p)
  for res in p_list:
   res.join()
  print(d)
  print(l)

程序执行的结果为:

[0, 1, 2, 3, 4, 2100]
[0, 1, 2, 3, 4, 2100, 7632]
[0, 1, 2, 3, 4, 2100, 7632, 5788]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904, 7888]
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904, 7888, 7612]
{1: '1', '2': 2, 0.25: None}
[0, 1, 2, 3, 4, 2100, 7632, 5788, 6340, 5760, 7072, 7540, 3904, 7888, 7612]

进程锁与进程池

进程锁

进程也有一个锁,what?进程不都独立了吗?不涉及同时修改同一个数据,怎么还会有锁呢?

闲了来看看它的表现形式,几乎和线程是一模一样的

from multiprocessing import Process, Lock

def f(l, i):
 l.acquire()
 try:
  print('hello world', i)
 finally:
  l.release()

if __name__ == '__main__':
 lock = Lock()
 for num in range(10):
  Process(target=f, args=(lock, num)).start()

程序执行的结果为:

hello world 3
hello world 1
hello world 2
hello world 5
hello world 7
hello world 4
hello world 0
hello world 6
hello world 8
hello world 9

那这种锁有什么作用呢?

作用其实就是防止打印在屏幕上的信息发生错乱现象!

进程池

在上面的程序中,启动100个进程会发现变慢了,因为起一个进程就相当克隆了一份父进程的内存数据,如果父进程占一个G的内存空间,那我起100个进程,就相当于101G了,在这种情况下,开销是非常大的,就像起一个进程?E嚓又克隆了一个屋子,一会就把哈尔滨占满了,所以开销特别大,为了避免?E嚓起那么多的进程,把系统打趴下,所以这里有个进程池的限制。

进程池就是同一时间有多少进程在CPU运行。

进程池中有两个方法:

  • apply(同步执行,串行)
  • apply_async(异步执行、并行)
from multiprocessing import Process,Pool,freeze_support
import time
import os

def Foo(i):
 time.sleep(2)
 print("in process",os.getpid())
 return i+100

def Bar(arg):
 print('-->exec done:',arg)

if __name__ == '__main__':
 freeze_support()
 pool = Pool(5) # 允许进程池里同时放入5个进程

 for i in range(10):
  # pool.apply_async(func=Foo, args=(i,),callback=Bar) # callback 回调
  pool.apply(func=Foo, args=(i,)) # 串行
  # pool.apply_async(func=Foo, args=(i,)) # 并行

 print('end')

 pool.close()
 pool.join() # 进程池中进程执行完毕后再关闭,如果注释,那么程序直接关闭。

程序的执行结果为:

in process 7824
in process 6540
in process 7724
in process 8924
in process 9108
in process 7824
in process 6540

知识点扩充:

__name__  ==  '__main__'的作用是:

手动执行关于这段代码的程序,那么他下面的程序就会执行,如果是调用这段代码的程序时,那么它下面的程序就不会执行

以上就是深入理解Python 多线程的详细内容,更多关于Python 多线程的资料请关注三水点靠木其它相关文章!

Python 相关文章推荐
Google开源的Python格式化工具YAPF的安装和使用教程
May 31 Python
在dataframe两列日期相减并且得到具体的月数实例
Jul 03 Python
python 公共方法汇总解析
Sep 16 Python
win10环境下配置vscode python开发环境的教程详解
Oct 16 Python
vim自动补全插件YouCompleteMe(YCM)安装过程解析
Oct 21 Python
完美解决Django2.0中models下的ForeignKey()问题
May 19 Python
python定义类的简单用法
Jul 24 Python
Django分页器的用法你都了解吗
May 26 Python
python代码实现备忘录案例讲解
Jul 26 Python
68行Python代码实现带难度升级的贪吃蛇
Jan 18 Python
Python 图片添加美颜效果
Apr 28 Python
keras.layer.input()用法说明
Jun 16 #Python
python适合做数据挖掘吗
Jun 16 #Python
Python+PyQt5+MySQL实现天气管理系统
Jun 16 #Python
Python实现SMTP邮件发送
Jun 16 #Python
python语言中有算法吗
Jun 16 #Python
python爬虫可以爬什么
Jun 16 #Python
通过cmd进入python的步骤
Jun 16 #Python
You might like
php完全过滤HTML,JS,CSS等标签
2009/01/16 PHP
JS 无法通过W3C验证的处理方法
2010/03/09 Javascript
js写一个弹出层并锁屏效果实现代码
2012/12/07 Javascript
解决用jquery load加载页面到div时,不执行页面js的问题
2014/02/22 Javascript
js实现动画特效的文字链接鼠标悬停提示的方法
2015/03/02 Javascript
js比较日期大小的方法
2015/05/12 Javascript
使用nodejs开发cli项目实例
2015/06/03 NodeJs
JS+DIV+CSS实现的经典标签切换效果代码
2015/09/14 Javascript
jQuery+PHP+MySQL实现无限级联下拉框效果
2016/02/19 Javascript
什么是JavaScript注入攻击?
2016/09/14 Javascript
Javascript 调用 ActionScript 的简单方法
2016/09/22 Javascript
AngularJS中$injector、$rootScope和$scope的概念和关联关系深入分析
2017/01/19 Javascript
详解让sublime text3支持Vue语法高亮显示的示例
2017/09/29 Javascript
vue刷新和tab切换实例
2018/02/11 Javascript
解决淘宝cnpm 安装后cnpm不是内部或外部命令的问题
2018/05/17 Javascript
Element-ui自定义table表头、修改列标题样式、添加tooltip、:render-header使用
2019/04/11 Javascript
关于vue路由缓存清除在main.js中的设置
2019/11/06 Javascript
Vue 一键清空表单的实现方法
2020/02/07 Javascript
[00:35]TI7不朽珍藏III——寒冰飞龙不朽展示
2017/07/15 DOTA
Python解决N阶台阶走法问题的方法分析
2017/12/28 Python
Python获取指定字符前面的所有字符方法
2018/05/02 Python
Python内存读写操作示例
2018/07/18 Python
Python 判断图像是否读取成功的方法
2019/01/26 Python
使用jupyter Nodebook查看函数或方法的参数以及使用情况
2020/04/14 Python
HTML5拖放功能_动力节点Java学院整理
2017/07/13 HTML / CSS
REISS美国官网:伦敦最受欢迎的时尚品牌
2019/08/16 全球购物
应届大学生求职的自我评价
2013/11/17 职场文书
计算机专业职业规划
2014/02/28 职场文书
信息总监管理职责范本
2014/03/08 职场文书
中学生操行评语
2014/04/24 职场文书
宣传口号大全
2014/06/16 职场文书
2019年入党思想汇报
2019/03/25 职场文书
总结一些Java常用的加密算法
2021/06/11 Java/Android
SpringBoot集成Redis,并自定义对象序列化操作
2021/06/22 Java/Android
MongoDB数据库之添删改查
2022/04/26 MongoDB
Java代码规范与质量检测插件SonarLint的使用
2022/08/05 Java/Android