深入理解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 linecache.getline()读取文件中特定一行的脚本
Sep 06 Python
浅谈Django REST Framework限速
Dec 12 Python
python实现超市扫码仪计费
May 30 Python
tensorflow: variable的值与variable.read_value()的值区别详解
Jul 30 Python
python 移动图片到另外一个文件夹的实例
Jan 10 Python
Django使用Jinja2模板引擎的示例代码
Aug 09 Python
对django layer弹窗组件的使用详解
Aug 31 Python
python语言线程标准库threading.local解读总结
Nov 10 Python
Python3列表List入门知识附实例
Feb 09 Python
将labelme格式数据转化为标准的coco数据集格式方式
Feb 17 Python
Pandas搭配lambda组合使用详解
Jan 22 Python
Matplotlib绘制条形图的方法你知道吗
Mar 21 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 empty,isset,is_null判断比较(差异与异同)
2010/10/19 PHP
php的memcached客户端memcached
2011/06/14 PHP
基于php伪静态的实现详细介绍
2013/04/28 PHP
利用谷歌 Translate API制作自己的翻译脚本
2014/06/04 PHP
ThinkPHP有变量的where条件分页实例
2014/11/03 PHP
百度地图经纬度转换到腾讯地图/Google 对应的经纬度
2015/08/28 PHP
WordPress中制作导航菜单的PHP核心方法讲解
2015/12/11 PHP
Laravel4中的Validator验证扩展用法详解
2016/07/26 PHP
php使用curl详细解析及问题汇总
2016/08/11 PHP
php模式设计之观察者模式应用实例分析
2019/09/25 PHP
2007/12/23更新创意无限,简单实用(javascript log)
2007/12/24 Javascript
javascript入门基础之私有变量
2010/02/23 Javascript
Javascript学习笔记-详解in运算符
2011/09/13 Javascript
使用变量动态设置js的属性名
2014/10/19 Javascript
JavaScript+CSS实现的可折叠二级菜单实例
2016/02/29 Javascript
基于node实现websocket协议
2016/04/25 Javascript
jQuery Chart图表制作组件Highcharts用法详解
2016/06/01 Javascript
每日十条JavaScript经验技巧(一)
2016/06/23 Javascript
多种方式实现js图片预览
2016/12/12 Javascript
从零开始做一个pagination分页组件
2017/03/15 Javascript
node.js + socket.io 实现点对点随机匹配聊天
2017/06/30 Javascript
JS实现评价的星星功能
2017/08/20 Javascript
JS实现网页抢购功能(触发,终止脚本)
2017/11/27 Javascript
解决IE11 vue +webpack 项目中数据更新后页面没有刷新的问题
2018/09/25 Javascript
python中安装模块包版本冲突问题的解决
2017/05/02 Python
python多进程实现文件下载传输功能
2018/07/28 Python
postman模拟访问具有Session的post请求方法
2019/07/15 Python
PyTorch加载预训练模型实例(pretrained)
2020/01/17 Python
在HTML5中如何使用CSS建立不可选的文字
2014/10/17 HTML / CSS
纯CSS3实现的阴影效果
2014/12/24 HTML / CSS
详解如何使用CSS3中的结构伪类选择器和伪元素选择器
2020/01/06 HTML / CSS
西安众合通用.net笔试题
2013/03/18 面试题
党的群众路线教育实践活动总结报告
2014/04/28 职场文书
2015年商场工作总结
2015/04/27 职场文书
爱心捐助活动总结
2015/05/09 职场文书
《夸父追日》教学反思
2016/02/20 职场文书