深入理解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基础教程之基本数据类型和变量声明介绍
Aug 29 Python
用Python进行一些简单的自然语言处理的教程
Mar 31 Python
Python数组遍历的简单实现方法小结
Apr 27 Python
Python实现购物车功能的方法分析
Nov 10 Python
python复制列表时[:]和[::]之间有什么区别
Oct 16 Python
python之生产者消费者模型实现详解
Jul 27 Python
Python常用模块logging——日志输出功能(示例代码)
Nov 20 Python
Python函数参数定义及传递方式解析
Jun 10 Python
Python基于traceback模块获取异常信息
Jul 23 Python
用python实现一个简单的验证码
Dec 09 Python
python 基于selenium实现鼠标拖拽功能
Dec 24 Python
Python上下文管理器Content Manager
Jun 26 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 组件化编程技巧
2009/06/06 PHP
php数组函数序列之in_array() 查找数组值是否存在
2011/10/29 PHP
关于PHP自动判断字符集并转码的详解
2013/06/26 PHP
PHP-FPM实现性能优化
2016/03/31 PHP
php基于PDO连接MSSQL示例DEMO
2016/07/13 PHP
php微信公众号开发(3)php实现简单微信文本通讯
2016/12/15 PHP
Laravel框架模板继承操作示例
2018/06/11 PHP
Javascript var变量隐式声明方法
2009/10/19 Javascript
jquery 可拖拽的窗体控件实现代码
2010/03/21 Javascript
js下判断 iframe 是否加载完成的完美方法
2010/10/26 Javascript
javascript修改表格背景色实例代码分享
2013/12/10 Javascript
js实现键盘控制DIV移动的方法
2015/01/10 Javascript
基于javascript实现文字无缝滚动效果
2016/03/22 Javascript
原生js实现无限循环轮播图效果
2017/01/20 Javascript
jquery精度计算代码 jquery指定精确小数位
2017/02/06 Javascript
在 Node.js 中使用 async 函数的方法
2017/11/17 Javascript
JavaScript 隐性类型转换步骤浅析
2018/03/15 Javascript
详解React Native 屏幕适配(炒鸡简单的方法)
2018/06/11 Javascript
javascript 使用sleep函数的常见方法详解
2020/04/26 Javascript
[01:41]DOTA2 2015国际邀请赛中国区预选赛第三日战报
2015/05/28 DOTA
在Python中操作列表之list.extend()方法的使用
2015/05/20 Python
PyQt5每天必学之带有标签的复选框
2018/04/19 Python
python opencv判断图像是否为空的实例
2019/01/26 Python
PyCharm安装Markdown插件的两种方法
2019/06/24 Python
python gensim使用word2vec词向量处理中文语料的方法
2019/07/05 Python
django model通过字典更新数据实例
2020/04/01 Python
波兰品牌内衣及泳装网上商店:Astratex.pl
2017/02/03 全球购物
Hotter Shoes英国官网:英伦风格,舒适的鞋子
2017/12/28 全球购物
static关键字的用法
2013/10/07 面试题
本科毕业生求职自荐信
2014/02/03 职场文书
高中生的自我评价
2014/03/04 职场文书
安全教育感言
2014/03/04 职场文书
党员对照检查材料
2014/09/22 职场文书
个人欠款协议书范本2014
2014/11/02 职场文书
装饰施工员岗位职责
2015/04/11 职场文书
2016年教师学习廉政准则心得体会
2016/01/20 职场文书