『Python』进程同步
1. Lock(互斥锁)
是可用的最低级的同步指令。Lock
处于锁定状态时,不被其他的线程拥有。
from multiprocessing import Process, Value, Lock
def func1(num, lock: Lock):
lock.acquire()
print(num.value)
num.value += 1
lock.release()
if __name__ == '__main__':
lock = Lock() # 创建锁对象
val = Value('i', 0)
proc = [Process(target=func1, args=(val, lock)) for i in range(10)]
for p in proc:
p.start()
for p in proc:
p.join()
print(val.value) # 10
上面获取锁和释放锁可以用with语句简化,它可以自动获取和释放锁
def func1(num, lock):
with lock:
num.value += 1
输出:
0
1
2
3
4
5
6
7
8
9
10
2. RLock(递归锁)
是一个可以被同一个进程请求多次的同步指令。RLock
使用了“拥有的进程”和“递归等级”的概念,处于锁定状态时,RLock
被某个进程拥有。拥有RLock
的进程可以多次调用acquire()
,释放锁时需要调用release()
相同次数,其他进程在此期间得不到锁。
主要就是对于进程处理中会有一些比较复杂的代码逻辑过程,比如很多层的函数调用,而这些函数其实都需要进行加锁保护数据访问。
这样就可能会反复的多次加锁,因而用RLock
就可以进行多次加锁、解锁,直到最终锁被释放;而如果用普通的Lock
,当你一个函数A
已经加锁,它内部调用另一个函数B
,如果B
内部也会对同一个锁加锁,那么这种情况就也会导致死锁。而RLock
可以解决这个问题。
from multiprocessing import Process
from multiprocessing import RLock, Lock, Value
def f(v, lock):
with lock:
g(v, lock)
def g(v, lock):
with lock:
for _ in range(10):
v.value += 1
print(f"g()===>{v.value}")
if __name__ == '__main__':
# lock = Lock() # 如果用Lock,就会出现死锁
lock = RLock()
v = Value("i", 0)
p = Process(target=f, args=(v, lock))
p.start()
p.join()
print(v.value) # 10
输出:
g()===>1
g()===>2
g()===>3
g()===>4
g()===>5
g()===>6
g()===>7
g()===>8
g()===>9
g()===>10
10
3. Semaphore(信号量同步)
互斥锁同时只允许一个进程更改数据,而信号量Semaphore
是同时允许一定数量的进程更改数据 。
信号量同步基于内部计数器,每调用一次acquire()
,计数器减1
;每调用一次release()
,计数器加1
,当计数器为0
时,acquire()
调用被阻塞。
这是Dijkstra信号量概念PV
操作的Python实现。信号量同步机制适用于访问像服务器这样的有限资源,它与进程池的概念很像,但是要区分开,信号量涉及到加锁的概念。
【示例·KTV】
假设商场里有4个迷你唱吧,所以同时可以进去4个人,如果来了第五个人就要在外面等待,等到有人出来才能再进去玩。
import random
import time
from multiprocessing import Process, Semaphore
def ktv(i, mutex):
with mutex:
print(f"person{i}进来唱歌了")
time.sleep(random.randint(1, 5)) # 唱歌时间
print(f"person{i}从ktv出去了")
if __name__ == '__main__':
mutex = Semaphore(4)
for i in range(5):
Process(target=ktv, args=(i, mutex)).start()
输出:
person2进来唱歌了
person0进来唱歌了
person1进来唱歌了
person3进来唱歌了
person1从ktv出去了
person4进来唱歌了
person3从ktv出去了
person0从ktv出去了
person2从ktv出去了
person4从ktv出去了
信号量和锁有点类似,它们之间的区别,信号量,相当于计算器
信号量: 锁 + 计数器
acquire()
: 计数器 - 1,计数器减为0时阻塞
release()
: 计数器+ 1
4. Condition(条件同步)
Condition
被称为条件变量,除了提供与Lock
类似的acquire()
和release()
方法外,还提供了wait()
和notify()
方法,还有notify_all()
与wait_for()
方法。
【基本原理】
可以认为Condition
对象维护了一个锁(Lock
/RLock
)和一个waiting
池。进程通过acquire()
获得Condition
对象,当调用wait()
方法时,进程会释放Condition
内部的锁并进入blocked
状态,同时在waiting
池中记录这个进程。当调用notify()
方法时,Condition
对象会从waiting
池中挑选一个进程,通知其调用acquire()
方法尝试取到锁。
Condition
对象的构造函数可以接受一个Lock
/RLock
对象作为参数,如果没有指定,则Condition
对象会在内部自行创建一个RLock
。
除了notify()
方法外,Condition
对象还提供了notify_all()
方法,可以通知waiting
池中的所有进程尝试acquire
内部锁。由于上述机制,处于waiting
状态的进程只能通过notify()
方法唤醒,所以notify_all()
的作用在于防止有的线程永远处于沉默状态。
【示例·一人问一人答】
import time
from multiprocessing import Process, Condition
class Male(Process):
def __init__(self, cond):
super().__init__()
self.__cond = cond
def run(self):
with self.__cond:
time.sleep(2) # 防止死锁
print("昔日龌龊不足夸")
self.__cond.notify()
self.__cond.wait()
print("春风得意马蹄疾")
self.__cond.notify()
class Female(Process):
def __init__(self, cond):
super().__init__()
self.__cond = cond
def run(self):
with self.__cond:
self.__cond.wait()
print("今朝放荡思无涯")
self.__cond.notify()
self.__cond.wait()
print("一日看尽长安花")
if __name__ == '__main__':
cond = Condition()
male = Male(cond)
female = Female(cond)
female.start()
male.start()
输出:
昔日龌龊不足夸
今朝放荡思无涯
春风得意马蹄疾
一日看尽长安花
5. Event(事件)
Python进程的事件用于主进程控制其他进程的执行,事件对象管理一个内部标志,默认为False
,主要提供了以下方法:
is_set():
当且仅当内部标志为
True
时,返回True
set():
将内部标志设置为
True
。所有正在等待这个事件的线程将被唤醒。clear():
将内部标志设置为
False
。wait(timeout=None):
阻塞进程直到内部变量为
True
。如果调用时内部标志为True
,将立即返回,否则将阻塞进程,直到调用set()
方法将标志设置为True
或者超过设置的超时时间(秒)。
【示例·模拟红绿灯】
车看作是一个进程,wait()
等红灯,根据状态变化,wait()
遇到True
信号,就非阻塞,遇到False
,就阻塞;交通灯也是一个进程 红灯:False
绿灯:True
。
import random
import time
from multiprocessing import Process, Event
class TrafficLight(Process):
def __init__(self, e):
super().__init__(name="TrafficLight")
self.e = e
def run(self):
print('\033[1;31m红灯亮\033[0m')
time.sleep(2)
while True:
if not self.e.is_set():
print('\033[1;32m绿灯亮\033[0m')
self.e.set()
else:
print('\033[1;31m红灯亮\033[0m')
self.e.clear()
time.sleep(2)
class Car(Process):
def __init__(self, i, e):
super().__init__(name="Car")
self.i = i
self.e = e
def run(self):
if not self.e.is_set():
print(f"car{self.i}正在等待")
self.e.wait()
print(f"car{self.i}通过路口")
if __name__ == '__main__':
e = Event()
TrafficLight(e).start()
for i in range(50):
time.sleep(random.randrange(0, 5, 2))
Car(i, e).start()
部分输出:
红灯亮
car0正在等待
绿灯亮
car0通过路口
红灯亮
绿灯亮
car1通过路口
红灯亮
car2正在等待
绿灯亮
car2通过路口
红灯亮
car4正在等待
car3正在等待
绿灯亮
car3通过路口
car4通过路口
car5通过路口
6. IPC的解决方案
当使用多个进程时,通常使用消息传递来进行进程之间的通信(IPC),并必须避免使用任何同步原语(如锁)。对于传递消息,可以使用Pipe()
(用于两个进程之间的连接)或队列Queue
(允许多个生产者和消费者)。
6.1 Pipe(不推荐使用)
Pipe
常用来在两个进程间通信,两个进程分别位于管道的两端,默认是双向的。
创建管道
语法:
Pipe(duplex=True)
功能:在两个进程之间创建一条管道,并返回元组
(conn1,conn2)
,其中conn1
,conn2
表示管道两端的连接对象,强调一点:必须在产生Process
对象之前产生管道参数:
duplex
默认为True
,表示管道是全双工的,如果duplex
为False
,则conn1
只能用于接收,conn2
只能用于发送。from multiprocessing import Pipe # 创建双向的Pipe
conn1, conn2 = Pipe(True)
# 创建单向Pipe,conn4只能send(),conn3只能recv()
conn3, conn4 = Pipe(False)conn1.recv()
接收conn2.send(obj)
发送的对象。如果没有消息可接收,recv
方法会一直阻塞。如果连接的另外一端已经关闭,那么recv
方法会抛出EOFError
。conn1.send(obj)
通过连接发送对象。obj
是与序列化兼容的任意对象。conn1.close()
关闭连接。如果conn1
被垃圾回收,将自动调用此方法conn1.poll([timeout])
如果连接上的数据可用,返回True
。timeout
指定等待的最长时限。如果省略此参数,方法将立即返回结果。如果将timeout
设为None
,操作将无限期地等待数据到达。
【示例】
from multiprocessing import Process, Pipe
import time
class Sever(Process):
def __init__(self, sever):
super().__init__()
self.sever = sever
def run(self):
n = 0
print("服务器开启...")
while self.sever.poll(0.01):
obj = self.sever.recv()
n += 1
print(f"第{n}行:{obj!r}")
else:
print("服务器关闭...")
class Client(Process):
def __init__(self, client):
super().__init__()
self.client = client
def run(self):
self.client.send(True)
self.client.send(1)
self.client.send("我自俯察世人,你亦怜惜众生")
self.client.send({"称号": "无极剑圣", "姓名": "易"})
self.client.close()
if __name__ == '__main__':
sever, client = Pipe()
Sever(sever).start()
Client(client).start()
输出:
服务器开启...
第1行:True
第2行:1
第3行:'我自俯察世人,你亦怜惜众生'
第4行:{'称号': '无极剑圣', '姓名': '易'}
服务器关闭...
6.2 Queue(推荐使用)
创建队列
语法:
Queue(maxsize=0)
maxsize
是一个整数,用于设置可以放入队列的项目数的上限。达到此大小后,插入将阻止,直到消耗队列项。如果maxsize
小于或等于零,则队列大小为无限大。q.get(block=True, timeout=None)
返回q
中的一个项目。如果q
为空,此方法将阻塞,直到队列中有项目可用为止。block
用于控制阻塞行为,默认为True
。 如果设置为False
,将引发Queue.Empty
异常(定义在Queue
模块中)。timeout
是可选超时时间,用在阻塞模式中,如果在指定的时间间隔内没有项目变为可用,将引发Queue.Empty
异常。q.get_nowait()
同
q.get(False)
q.put(obj, block=True, timeout=None)
将item
放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block
控制阻塞行为,默认为True
。如果设置为Fals
e,将引发Queue.Full
异常(定义在Queue
库模块中)。timeout
指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full
异常。q.put_nowait(obj)
同q.put(obj,False)
q.qsize()
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发
NotImplementedError
异常。q.empty()
如果调用此方法时
q
为空,返回True
。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。q.full()
如果
q
已满,返回为True
. 由于并发的存在,结果也可能是不可靠的(参考q.empty()
方法)。q.close()
关闭队列,防止队列中加入更多数据。调用此方法时,后台线程将继续写入那些已入队列但尚未写入的数据,但将在此方法完成时马上关闭。如果
q
被垃圾收集,将自动调用此方法。关闭队列不会在队列使用者中生成任何类型的数据结束信号或异常,例如,如果某个使用者正被阻塞在get()
操作上,关闭生产者中的队列不会导致get()
方法返回错误。q.cancel_join_thread()
不会再进程退出时自动连接后台线程。这可以防止
join_thread()
方法阻塞。q.join_thread()
连接队列的后台线程。此方法用于在调用
q.close()
方法后,等待所有队列项被消耗。
【示例·生产者消费者模型】
import os
import random
import time
from multiprocessing import Process, Queue
class Producer(Process):
def __init__(self, q):
super().__init__()
self.queue = q
def run(self):
for i in range(10):
time.sleep(random.randint(1, 3))
res = f"包子{i}"
self.queue.put(res)
print(f'\033[1;31m{os.getpid()} 生产了 {res}\033[0m')
self.queue.put(None)
class Consumer(Process):
def __init__(self, q):
super().__init__()
self.queue = q
def run(self):
while True:
res = self.queue.get()
if res is None: break
time.sleep(random.randint(2, 4))
print(f'\033[1;32m{os.getpid()} 吃了 {res}\033[0m')
if __name__ == '__main__':
q = Queue()
Producer(q).start()
Consumer(q).start()
『Python』进程同步的更多相关文章
- 『Python』__getattr__()特殊方法
self的认识 & __getattr__()特殊方法 将字典调用方式改为通过属性查询的一个小class, class Dict(dict): def __init__(self, **kw) ...
- 『Python』 ThreadPool 线程池模板
Python 的 简单多线程实现 用 dummy 模块 一句话就可以搞定,但需要对线程,队列做进一步的操作,最好自己写个线程池类来实现. Code: # coding:utf-8 # version: ...
- 『Python』Python 调用 ZoomEye API 批量获取目标网站IP
#### 20160712 更新 原API的访问方式是以 HTTP 的方式访问的,根据官网最新文档,现在已经修改成 HTTPS 方式,测试可以正常使用API了. 0x 00 前言 ZoomEye 的 ...
- 『Python』为什么调用函数会令引用计数+2
一.问题描述 Python中的垃圾回收是以引用计数为主,分代收集为辅,引用计数的缺陷是循环引用的问题.在Python中,如果一个对象的引用数为0,Python虚拟机就会回收这个对象的内存. sys.g ...
- 『Python』库安装
1.安装指定版本的tensorflow 虽然官网有4种安装方式,并且推荐用anaconda的方式,但是有时候我们需要指定版本的tensorflow,而pip可以做到. 比如我装的是anaconda3. ...
- 『Python』装饰器
一.参考 作者:zhijun liu 链接:https://www.zhihu.com/question/26930016/answer/99243411 来源:知乎 建议大家去原答案浏览 二.装饰器 ...
- 『Python』多进程处理
尝试学习python的多进程模组,对比多线程,大概的区别在: 1.多进程的处理速度更快 2.多进程的各个子进程之间交换数据很不方便 多进程调用方式 进程基本使用multicore() 进程池优化进程的 ...
- 『Python』内存分析_list和array
零.预备知识 在Python中,列表是一个动态的指针数组,而array模块所提供的array对象则是保存相同类型的数值的动态数组.由于array直接保存值,因此它所使用的内存比列表少.列表和array ...
- 『Python』VS2015编译源码注意事项
一.2.5.6版本源码编译 解压 Python-2.5.6.tgz 进入 Pcbuild8 文件夹,使用 vs 2013 打开 pybuild.sln (vs 解决方案),进入 vs2015IDE 环 ...
随机推荐
- js清空input file的值
原文:js清空input file的值 在做选择本地图片上传的功能时遇到一个问题,第一次点file按钮选择图片完成会触发onchange事件,获取文件后动态在界面上创建img标签展示,但把创建的img ...
- JSOUP教程目录
入门: 1.解析和遍历一个HTML文档 输入: 2.解析一个HTML字符串 3.解析一个body片断 4.从一个URL加载一个Document 5.从一个文件加载一个文档 数据抽取: 6.使用DOM方 ...
- 【springcloud】配置中心(Config-Server)
转自:https://blog.csdn.net/pengjunlee/article/details/88061736 参考文章 Spring Cloud 配置中心为分布式系统中的服务器端和客户端提 ...
- 栈(Stack)
特点: 栈最大的特点就是后进先出(LIFO).对于栈中的数据来说,所有操作都是在栈的顶部完成的,只可以查看栈顶部的元素,只能够向栈的顶部压入数据,也只能从栈的顶部弹出数据. 实现: 利用一个单链表来实 ...
- 如何在指定的地址上创建C++对象
如果已经掌握在静态存储区上创建对象的方法,那么可以扩展一下,可以在任意地址上创建C++对象. 解决方案:-在类中重载new/delete操作符-在new的操作符重载函数中返回指定的地址-在delete ...
- JAVA中的clone方法剖析
原文出自:http://blog.csdn.net/shootyou/article/details/3945221 java中也有这么一个概念,它可以让我们很方便的"制造"出一个 ...
- 自己封装一个Object.freeze()方法
1.遍历所有属性和方法 2.修改遍历到的属性的描述 3.Object.seal() Object.defineProperty(Object,'freezePolyfill',{ value:func ...
- Linux从头学10:三级跳过程详解-从 bootloader 到 操作系统,再到应用程序
作 者:道哥,10+年的嵌入式开发老兵. 公众号:[IOT物联网小镇],专注于:C/C++.Linux操作系统.应用程序设计.物联网.单片机和嵌入式开发等领域. 公众号回复[书籍],获取 Linux. ...
- golang操作etcd
etcd是近几年比较火热的一个开源的.分布式的键值对数据存储系统,提供共享配置.服务的注册和发现,本文主要介绍etcd的安装和使用. etcd介绍 etcd是使用Go语言开发的一个开源的.高可用的分布 ...
- Django使用富文本编辑器ckediter
1 - 安装 pip install django-ckeditor 2 - 注册APP ckeditor 3 - 由于djang-ckeditor在ckeditor-init.js文件中使用了JQu ...