深入理解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 相关文章推荐
python处理中文编码和判断编码示例
Feb 26 Python
Python通过PIL获取图片主要颜色并和颜色库进行对比的方法
Mar 19 Python
Python中Collections模块的Counter容器类使用教程
May 31 Python
python如何使用正则表达式的前向、后向搜索及前向搜索否定模式详解
Nov 08 Python
浅谈Python 多进程默认不能共享全局变量的问题
Jan 11 Python
Python安装Flask环境及简单应用示例
May 03 Python
flask框架自定义过滤器示例【markdown文件读取和展示功能】
Nov 08 Python
TensorFlow使用Graph的基本操作的实现
Apr 22 Python
查看jupyter notebook每个单元格运行时间实例
Apr 22 Python
python操作ini类型配置文件的实例教程
Oct 30 Python
matplotlib制作雷达图报错ValueError的实现
Jan 05 Python
Python命令行参数argv和argparse该如何使用
Feb 08 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新手上路(十三)
2006/10/09 PHP
一家之言的经验之谈php+mysql扎实个人基本功
2008/03/27 PHP
服务器上配置PHP运行环境教程
2015/02/12 PHP
PHP递归实现汉诺塔问题的方法示例
2017/11/25 PHP
破除网页鼠标右键被禁用的绝招大全
2006/12/27 Javascript
用js实现上传图片前的预览(TX的面试题)
2007/08/14 Javascript
基于jQuery的试卷自动排版系统实现代码
2011/01/06 Javascript
js的压缩及jquery压缩探讨(提高页面加载性能/保护劳动成果)
2013/01/29 Javascript
Jquery如何实现点击时高亮显示代码
2014/01/22 Javascript
js实现点击链接后延迟3秒再跳转的方法
2015/06/05 Javascript
JavaScript统计网站访问次数的实现代码
2015/11/18 Javascript
vue.js入门教程之基础语法小结
2016/09/01 Javascript
前端自动化开发之Node.js的环境搭建教程
2017/04/01 Javascript
详解使用vue脚手架工具搭建vue-webpack项目
2017/05/10 Javascript
jQuery实现的点击显示隐藏下拉菜单功能完整示例
2019/05/17 jQuery
[04:22]DSPL第二期精彩集锦:残血反杀!
2014/12/10 DOTA
跟老齐学Python之编写类之三子类
2014/10/11 Python
Python编程实现数学运算求一元二次方程的实根算法示例
2017/04/02 Python
Python使用SQLite和Excel操作进行数据分析
2018/01/20 Python
python机器人行走步数问题的解决
2018/01/29 Python
Python基于win32ui模块创建弹出式菜单示例
2018/05/09 Python
python数据抓取3种方法总结
2021/02/07 Python
html5中JavaScript removeChild 删除所有节点
2014/05/16 HTML / CSS
为智能设备设计个性化保护套网站:caseable
2017/01/05 全球购物
土建资料员岗位职责
2014/01/04 职场文书
酒店端午节促销方案
2014/02/18 职场文书
进口业务员岗位职责
2014/04/06 职场文书
小学德育工作经验交流材料
2014/05/22 职场文书
高三励志标语
2014/06/05 职场文书
2014党员民主评议个人思想剖析发言
2014/09/19 职场文书
个人批评与自我批评
2014/10/15 职场文书
教师党的群众路线教育实践活动学习心得体会
2014/10/30 职场文书
2015秋季开学典礼致辞
2015/07/16 职场文书
深入理解CSS 中 transform matrix矩阵变换问题
2021/08/30 HTML / CSS
详解Oracle数据库中自带的所有表结构(sql代码)
2021/11/20 Oracle
Java实现经典游戏泡泡堂的示例代码
2022/04/04 Java/Android