在Python中,进程之间默认是不共享内存的。每个进程都有自己独立的内存空间,这意味着在一个进程中对数据的修改不会影响到另一个进程中的同名数据。然而,Python提供了几种方式来实现进程间的数据共享:
使用 multiprocessing 模块: Python 的 multiprocessing 模块提供了多种方式来实现进程间通信(IPC),其中包括:
使用文件或数据库: 进程可以通过读写文件或操作数据库来交换信息。这种方法相对简单但可能受到I/O性能限制。
使用消息传递机制:如队列(Queue)和管道(Pipe),这些也是 multiprocessing 提供的功能。它们允许将消息从一个进程传递到另一个,虽然严格意义上不是共享内存,但它们是进行数据交换和任务协调非常有效的手段。
下面是一个简单示例代码说明,在不使用Value的情况下
from multiprocessing import Process, Lock, current_process
import time
def process_with_shared_resource(shared_resource, lock):
name = current_process().name
lock.acquire()
try:
print(f"{name} 获取当前共享变量: {shared_resource}")
shared_resource += 1
time.sleep(0.1)
print(f"{name} 更新后的共享变量: {shared_resource}")
finally:
lock.release()
if __name__ == "__main__":
# 创建一个共享的资源和锁
shared_resource = 0
lock = Lock()
# 创建多个进程
processes = []
for _ in range(5):
p = Process(target=process_with_shared_resource, args=(shared_resource, lock))
processes.append(p)
p.start()
# 等待所有进程完成
for p in processes:
p.join()
print(f"最终变量值: {shared_resource}")
输出:
Process-3 获取当前共享变量: 0
Process-3 更新后的共享变量: 1
Process-1 获取当前共享变量: 0
Process-1 更新后的共享变量: 1
Process-2 获取当前共享变量: 0
Process-2 更新后的共享变量: 1
Process-4 获取当前共享变量: 0
Process-4 更新后的共享变量: 1
Process-5 获取当前共享变量: 0
Process-5 更新后的共享变量: 1
最终变量值: 0
看到,每个进程都有自己的变量shared_resource,并且初始化值都为0。
优化使用Value后:
from multiprocessing import Process, Lock, current_process, Value
import time
def process_with_shared_resource(shared_resource, lock):
name = current_process().name
lock.acquire()
try:
print(f"{name} 获取当前共享变量: {shared_resource.value}")
shared_resource.value += 1
time.sleep(0.1)
print(f"{name} 更新后的共享变量: {shared_resource.value}")
finally:
lock.release()
if __name__ == "__main__":
# 创建一个共享的资源和锁
shared_resource = Value('i', 0)
lock = Lock()
# 创建多个进程
processes = []
for _ in range(5):
p = Process(target=process_with_shared_resource, args=(shared_resource, lock))
processes.append(p)
p.start()
# 等待所有进程完成
for p in processes:
p.join()
print(f"最终变量值: {shared_resource.value}")
输出:
Process-1 获取当前共享变量: 0
Process-1 更新后的共享变量: 1
Process-4 获取当前共享变量: 1
Process-4 更新后的共享变量: 2
Process-5 获取当前共享变量: 2
Process-5 更新后的共享变量: 3
Process-2 获取当前共享变量: 3
Process-2 更新后的共享变量: 4
Process-3 获取当前共享变量: 4
Process-3 更新后的共享变量: 5
最终变量值: 5
结果正确了
在前面的学习中我们提到了一个概念,就是主进程的代码执行结束以后,主进程并没有结束的,因为主进程需要等待所有子进程运行代码结束以后,主进程通过系统调用回收子进程的资源,紧接着主进程才进行系统调用回收当前进程的资源。
那么,主进程怎么在数据隔离的情况下知道每一个子进程是什么时候结束的呢?
注意:子进程是完全有可能存在input,recv这样的阻塞代码情况的。实际上,父进程不可能预判到每个子进程什么时候结束的,但是可以让子进程在结束的时候发出一个信号告诉父进程,它结束了。父进程接受到该子进程的结束信号就可以通过系统调用回收子进程的资源了。而这个发出信号与接收信号的过程,就是进程间的通信(IPC)了。
进程间通信(Inter-Process Communication,IPC)是操作系统中允许不同进程之间交换信息的一种机制。在多任务操作系统中,IPC对于实现不同程序组件之间的协作至关重要。由于每个进程都有自己独立的内存空间,所以他们不能直接共享内存中的数据。Python提供了多种机制来实现进程间的通信,主要通过 multiprocessing 模块来完成。
管道是一种最基本的进程间通信方式。在Python中,multiprocessing 模块的 Pipe() 函数可以创建一对连接对象,这两个对象可以在不同进程间通过发送和接收消息来通信。管道可以是单向的(半双工)或双向的(全双工)。
创建管道:调用multiprocessing.Pipe()将创建一对可以用于进程间通信的文件描述符。
读写通信:一个进程使用一个文件描述符的一端来发送数据,另一个进程使用另一个文件描述符的另一端来接收数据。
半双工通信:数据只能在一个方向上流动,这意味着发送方和接收方不能同时发送数据,但可以交替进行。
关闭管道:通信完成后,应该关闭管道以释放系统资源。
from multiprocessing import Process, Pipe, current_process
def child(conn):
str_send = "Hello ~"
print(f'{current_process().name} 发送消息:{str_send}')
conn.send(str_send)
conn.send(str_send)
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
p = Process(target=child, args=(child_conn,))
p.start()
# recv会阻塞调用,直到收到消息
print(f"{current_process().name} 接受到消息:{parent_conn.recv()}")
print(f"{current_process().name} 接受到消息:{parent_conn.recv()}")
# print(f"{current_process().name} 接受到消息:{parent_conn.recv()}") # 阻塞接受消息
parent_conn.close() # 关闭父进程端的管道
p.join() # 等待子进程结束
child函数,它将通过管道发送一条消息。parent_conn和child_conn。child_conn传递给它。child函数,通过管道发送了一条消息。parent_conn.recv()来接收消息,然后打印这条消息。注意,multiprocessing.Pipe()主要用于进程间的通信,而不是线程间的通信。
队列是用于多个生产者(发送者)和消费者(接收者)的典型场景。它是线程和进程安全的。通过使用 multiprocessing.Queue,可以在进程间安全地交换消息或其他数据。
from multiprocessing import Process, Queue, current_process
def worker(queue):
while True:
args = queue.get() # 阻塞调用,直到队列中有数据
# 这里可以处理接收到的参数
print(f"{current_process().name} 接受到消息到: {args}")
if args is None: # 接收到退出信号
break
if __name__ == '__main__':
# 创建一个队列
q = Queue()
# 启动一个工作进程
p = Process(target=worker, args=(q,))
p.start()
# 将数据放入队列
for i in range(10):
print(f"{current_process().name} 发送消息: {i}")
q.put(i)
q.put(None)
# 等待队列中的所有任务被处理完成
p.join()
print("结束")
multiprocessing 模块还支持通过共享内存来进行进程间通信,主要通过 Value 或 Array。这允许创建一个可以在多个进程间共享的变量。
最开始的代码例子便使用的是Value模块
multiprocessing 提供了一个 Manager() 方法,用于创建一个管理器对象,该对象支持将Python对象放在服务器进程中,以便不同进程可以通过代理的方式访问。
from multiprocessing import Process, Manager, current_process
def f(d, l):
d[1] = '1'
d['2'] = 2
d[0.25] = None
l.reverse()
if __name__ == '__main__':
with Manager() as manager:
d = manager.dict()
l = manager.list(range(10))
print(f"{current_process().name} dict: {d}")
print(f"{current_process().name} list: {l}")
p = Process(target=f, args=(d, l))
p.start()
p.join()
print(f"{current_process().name} dict: {d}")
print(f"{current_process().name} list: {l}")
# 子进程已经修改了变量
输出:
MainProcess dict: {}
MainProcess list: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
MainProcess dict: {1: '1', '2': 2, 0.25: None}
MainProcess list: [9, 8, 7, 6, 5, 4, 3, 2, 1, 0]
进程间的数据隔离是操作系统设计中的一个核心概念,它确保了不同进程的内存空间和数据是相互独立的,从而防止了进程间的非授权数据访问和潜在的数据冲突。以下是实现进程间数据隔离的一些关键机制:
独立的地址空间:
内存保护:
分页机制:
执行上下文:
文件描述符和文件权限:
系统调用:
用户和组ID:
命名空间:
安全模块和沙箱:
权限控制:
AppArmor或SELinux: