深入理解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 相关文章推荐
仅用500行Python代码实现一个英文解析器的教程
Apr 02 Python
Python Paramiko模块的安装与使用详解
Nov 18 Python
详解Python中表达式i += x与i = i + x是否等价
Feb 08 Python
Python基于tkinter模块实现的改名小工具示例
Jul 27 Python
python实现读取excel写入mysql的小工具详解
Nov 20 Python
Python实现按照指定要求逆序输出一个数字的方法
Apr 19 Python
从django的中间件直接返回请求的方法
May 30 Python
详解python单元测试框架unittest
Jul 02 Python
利用selenium爬虫抓取数据的基础教程
Jun 10 Python
感知器基础原理及python实现过程详解
Sep 30 Python
Keras Convolution1D与Convolution2D区别说明
May 22 Python
Django实现WebSocket在线聊天室功能(channels库)
Sep 25 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
将RTF格式的文件转成HTML并在网页中显示的代码
2006/10/09 PHP
一周学会PHP(视频)Http下载
2006/12/12 PHP
解析PHP处理换行符的问题 \r\n
2013/06/13 PHP
使用openssl实现rsa非对称加密算法示例
2014/01/24 PHP
php中把美国时间转为北京时间的自定义函数分享
2014/07/28 PHP
ThinkPHP查询返回简单字段数组的方法
2014/08/25 PHP
PHP中提问频率最高的11个面试题和答案
2014/09/02 PHP
PHP 芝麻信用接入的注意事项
2016/12/01 PHP
jQuery Jcrop插件实现图片选取功能
2011/11/23 Javascript
js使用html()或text()方法获取设置p标签的显示的值
2014/08/01 Javascript
js中substr,substring,indexOf,lastIndexOf,split,replace的用法详解
2015/11/09 Javascript
jQuery中ajax的load()与post()方法实例详解
2016/01/05 Javascript
AngularJS ng-bind-template 指令详解
2016/07/30 Javascript
NPM 安装cordova时警告:npm WARN deprecated minimatch@2.0.10: Please update to minimatch 3.0.2 or higher to
2016/12/20 Javascript
vue.js实现用户评论、登录、注册、及修改信息功能
2020/05/30 Javascript
webpack构建vue项目的详细教程(配置篇)
2017/07/17 Javascript
js基于FileSaver.js 浏览器导出Excel文件的示例
2017/08/15 Javascript
vue-router 前端路由之路由传值的方式详解
2019/04/30 Javascript
你了解vue3.0响应式数据怎么实现吗
2019/06/07 Javascript
微信小程序实现左侧滑栏过程解析
2019/08/26 Javascript
layer.open弹层查看缩略图的原图,自适应大小的实例
2019/09/05 Javascript
js判断在哪个浏览器打开项目的方法
2020/01/21 Javascript
python开发的小球完全弹性碰撞游戏代码
2013/10/15 Python
Python实现程序的单一实例用法分析
2015/06/03 Python
Python爬虫利用cookie实现模拟登陆实例详解
2017/01/12 Python
Python笔记之工厂模式
2019/11/20 Python
浅谈pytorch中的BN层的注意事项
2020/06/23 Python
python 多线程中join()的作用
2020/10/29 Python
详解通过focusout事件解决IOS键盘收起时界面不归位的问题
2019/07/18 HTML / CSS
Merrell美国官网:美国登山运动鞋品牌
2018/02/07 全球购物
高中化学教学反思
2014/01/13 职场文书
财政局个人年终总结
2015/03/03 职场文书
家长会感言
2015/08/01 职场文书
2016七夕情人节广告语
2016/01/28 职场文书
一次项目中Thinkphp绕过禁用函数的实战记录
2021/11/17 PHP
mysql中数据库覆盖导入的几种方式总结
2022/03/25 MySQL