深入理解Python 多线程
作者:追风的小蚂蚁 发布时间:2021-03-25 05:25:28
Python里的多线程是假的多线程,不管有多少核,同一时间只能在一个核中进行操作!利用Python的多线程,只是利用CPU上下文切换的优势,看上去像是并发,其实只是个单线程,所以说他是假的单线程。
那么什么时候用多线程呢?
首先要知道:
io操作不占用CPU
计算操作占CPU,像2+5=5
Python的多线程不适合CPU密集操作型的任务,适合io密集操作型的任务,例如:SocketServer
如果现在再有CPU密集操作型的任务,那该怎么办呢?
首先说,多进程的进程之间是独立的,然后注意了,python的线程用的是系统的原生线程,python的进程也是用系统的原生进程,那原生进程是由操作系统维护的,说白了python只是利用C原生代码库的接口咵嚓起了个进程,真正的进程管理还是由操作系统来完成的,那么操作系统本身有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: 6576function f
module name: __mp_main__
parent process: 6576
process id: 2232hello bob
其实这幅图片的意思是,每一个子进程都是由他父进程启动的。
进程间通讯
我们说两个进程之间的内存之间是相互独立的,那么这两个进程能够进行通信吗?说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了,在这种情况下,开销是非常大的,就像起一个进程咵嚓又克隆了一个屋子,一会就把哈尔滨占满了,所以开销特别大,为了避免咵嚓起那么多的进程,把系统打趴下,所以这里有个进程池的限制。
进程池就是同一时间有多少进程在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__'的作用是:
手动执行关于这段代码的程序,那么他下面的程序就会执行,如果是调用这段代码的程序时,那么它下面的程序就不会执行
来源:https://www.cnblogs.com/zhuifeng-mayi/p/9289622.html


猜你喜欢
- 背景背景是这样的, 我的家里台式机常年 休眠, 并配置了 Wake On Lan (WOL) 方便远程唤醒并使用.但是我发现, 偶尔台式机会
- TensorFlow训练时,遇到内存不断增长,最终导致内存不足,进程被杀死。在这里我不准备对造成这一现象的所有原因进行探讨,只是记录一下我在
- 准备工作我们需要把秒杀的商品加入购物车,因为脚本点击的是全选,所以不需要的商品要移出购物车。过程分析1.打开某宝网站;pq = webdri
- 命令首先数据库迁移的两大命令: python manage.py makemigrations & python manage.py
- 导读:有时候,为了开发项目,我们需要在一台服务器上部署MySql数据库服务器,然后使用本地电脑远程访问和管理MySql数据库,那么如何实现M
- Mysql InnoDB引擎数据页结构InnoDB 是 mysql 的默认引擎,也是我们最常用的,所以基于 InnoDB,学习页结构。而学习
- 本文实例为大家分享了Python3实现汉语转换为汉语拼音的具体代码,供大家参考,具体内容如下工具: Python3.6.2,pycharm1
- 我之前写过一篇基于JS的石头剪子布程序 《JavaScript实现的石头剪刀布游戏源码分享》,今天又基于Python写了一个实例,这里边的算
- 提问:如果想要通过爬虫程序去爬取”糗百“全站数据新闻数据的话,有几种实现方法?方法一:基于Scrapy框架中的Spider的递归爬去进行实现
- 比如我们有一张school表,里面有一个字段county_name,现在我们要查询county_name字段中包含a-w字母和数字以外字符的
- 取反运算符的原理:1.对3取反:(取4位二进制)①化为二进制:3→0011②对二进制结果取反:0011→1100③对结果先取反再加1:110
- 第一步肯定是打上SQL SERVER最新的安全补丁.如果这一步都没有做好,那我们也没有继续下去的必要了。 第二步是修改默认的1433端口,并
- 如下所示:class Login(QMainWindow): """登录窗口""
- 前言注释可以起到一个备注的作用,团队合作的时候,个人编写的代码经常会被多人调用,为了让别人能更容易理解代码的通途,使用注释是非常有效的。Py
- 如下所示:public function a(){ return $this->belongsTo('App\Mo
- Python标准库的OS模块对操作系统的API进行了封装,并且使用统一的API访问不同操作系统的相同功能。OS模块包含与操作系统的系统环境、
- 本文实例讲述了python实现读取excel文件中所有sheet操作。分享给大家供大家参考,具体如下:表格是这样的 实现把此文件所
- 本文实例讲述了JS异步函数队列功能。分享给大家供大家参考,具体如下:场景:做直播,会有入场消息,入场特效,用户如果有坐骑,需要给他展示几秒钟
- 页签的流行自从Yahoo!的首页引进页签(tab, 见下图)之后,这种可用性极佳的方式越来越受欢迎,用户也逐步习惯和喜欢上它,因为它可以在原
- 小白我用的是vue-cli的全家桶,在标签中加入v-drap则实现元素拖拽, 全局指令我是写在main.js中Vue.direc