并发编程(二)—— multiprocessing、进程池、进程间通信

multiprocessing 模块创建进程

进程创建方法

  1. 流程特点
    【1】 将需要子进程执行的事件封装为函数
    【2】 通过模块的Process类创建进程对象,关联函数
    【3】 可以通过进程对象设置进程信息及属性
    【4】 通过进程对象调用start启动进程
    【5】 通过进程对象调用join回收进程(防止僵尸进程出现)
  2. 基本接口使用
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数 (函数名)
     rgs 元组,用于给target函数位置传参
     kwargs 字典,给target函数键值传参
p.start()
功能 : 启动进程

注意:启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建

p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间(可选参数)

重要代码

单个子进程(无参数):

import multiprocessing as mp
from time import sleep

# 子进程函数
def fun():
    print("子进程开始")
    sleep(3)
    print("子进程结束")

# 创建进程对象
p = mp.Process(target=fun)#注意是函数名

# 启动进程
p.start()

# 父进程同时执行
sleep(2)
print('父进程干点事儿')

# 回收进程
p.join()

单个子进程(有参数):

from multiprocessing import Process
from time import sleep

# 子进程函数
def fun(name, age):
    sleep(2)
    print("I'm %s" % name)
    print("I'm %d years old" % age)

# 创建进程对象(两种方法传参)
# p = Process(target=fun, args=('xc', 25))  # 元组
p = Process(target=fun, kwargs={'name': 'xc', 'age': 25})  # 字典

# 启动进程
p.start()

# 回收进程
p.join()

多个子进程:

import multiprocessing as mp
from time import sleep
import os

# 子进程函数1
def fun1():
    sleep(2)
    print("eat")
    print(os.getppid(), '----', os.getpid())

# 子进程函数2
def fun2():
    sleep(3)
    print("sleep")
    print(os.getppid(), '----', os.getpid())

# 子进程函数3
def fun3():
    sleep(4)
    print("doudou")
    print(os.getppid(), '----', os.getpid())

things = [fun1, fun2, fun3]
jobs = []

for th in things:
    p = mp.Process(target=th)
    jobs.append(p)  # 保存进程对象
    p.start()

for i in jobs:
    i.join()

注意:

  • 使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响。
  • 子进程只运行target绑定的函数部分,其余内容均是父进程执行内容。
  • multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成。
  • multiprocessing创建的子进程中无法使用标准输入。
  1. 进程对象属性
  • p.name 进程名称

  • p.pid 对应子进程的PID号

  • p.is_alive() 查看子进程是否在生命周期

  • p.daemon 设置父子进程的退出关系

    • 如果设置为True则子进程会随父进程的退出而结束
    • 要求必须在start()前设置
    • 如果daemon设置成True 通常就不会使用 join(),因为join()含义是子进程退出(回收)时父进程不退出,但是daemon是父进程退出时子进程退出,矛盾
from multiprocessing import Process
from time import sleep,ctime

def tm():
  for i in range(3):
    sleep(2)
    print(ctime())

p = Process(target = tm,name = 'Tedu')

# p.daemon = True # 子进程会随父进程退出

p.start()
print("Name:",p.name)  # 名称
print("PID:",p.pid)  # PID
print("Is alive:",p.is_alive()) # 生命周期

进程池实现

  1. 必要性
    【1】 进程的创建和销毁过程消耗的资源较多
    【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
    【3】 进程池技术很好的解决了以上问题。

  2. 原理

创建一定数量的进程来处理事件,事件处理完进程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。

  1. 进程池实现

【1】 创建进程池对象,放入适当的进程

from multiprocessing import Pool

Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定(一般不写)

【2】 将事件加入进程池队列执行

pool.apply_async(func,args,kwds)
功能: 使用进程池执行 func事件
参数: func 事件函数
		 args 元组 给func按位置传参
		 kwds 字典 给func按照键值传参
返回值: 返回函数事件对象

【3】 关闭进程池

pool.close()
功能: 关闭进程池,之后不能加入新的事件

【4】 回收进程池中进程

pool.join()
功能: 回收进程池中进程,堵塞等待现有的进程执行完毕

示例代码:

from multiprocessing import Pool
from time import sleep, ctime

def worker(msg):
    sleep(2)
    print(ctime(), '---', msg)
    return ctime()

# 创建进程池
pool = Pool()#进程池中可以进行计算机核数量个进程(系统默认)
# pool = Pool(6)#进程池中可以同时进行6个进程

# 向进程池添加事件
for i in range(20):
    msg = "Hello %d" % i
    r=pool.apply_async(func=worker, args=(msg,))#元组只有一个值的话要加,

# 关闭进程池
pool.close()

# 回收进程池
pool.join()

# 获取事件返回值,函数没有返回值的话返回None
print(r.get())# 因为r会覆盖,所以这个地方输出的是最后一个r对应的函数返回值

进程间通信(IPC)

  1. 必要性:进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
  2. 常用进程间通信方法:管道、消息队列、共享内存、信号、信号量、套接字

管道通信(Pipe)

  1. 通信原理
    在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信
  2. 实现方法
from multiprocessing import Pipe
                
fd1,fd2 = Pipe(duplex = True)
功能:创建管道
参数:默认True表示双向管道(管道两侧都可读可写)
		   如果为False 表示单向管道(只能一侧读,另一侧写)
返回值:表示管道两端从的读写对象
		      如果是双向管道均可读写
		      如果是单向管道fd1只读  fd2只写

fd.recv()
功能:从管道获取内容(堵塞函数)
返回值:获取到的数据

fd.send(data)
功能:向管道写入内容
参数:要写入的数据

实现代码:

from multiprocessing import Process, Pipe
import os, time

# 创建管道对象
fd1, fd2 = Pipe()

def fun(name):
    time.sleep(3)
    # 向管道写入内容
    fd1.send({name:os.getpid()})

jobs = []
for i in range(5):
    p = Process(target=fun, args=(i,))
    jobs.append(p)
    p.start()

for i in range(5):
    # 读取管道内容
    data = fd2.recv()
    print(data)

for i in jobs:
    i.join()

消息队列

  1. 通信原理:在内存中建立通信模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
  2. 实现方法:
from multiprocessing import Queue
                    
q = Queue(maxsize=0)
功能:创建队列队列
参数:最多存放的消息个数
返回值:队列对象

q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
	 block  设置是否阻塞 ,默认True阻塞(消息队列满时),False为非阻塞
	 timeout 超时检测

q.get([block,timeout])
功能:从队列取出消息
参数:data 要存入的内容
     block  设置是否阻塞 ,默认True阻塞(消息队列为空),False为非阻塞
	 timeout 超时检测
返回值:返回获取到的内容

q.full()    判断队列是否为满
q.empty()   判断队列是否为空
q.qsize()   获取队列的消息个数
q.close()   关闭队列

实现代码:

from multiprocessing import Queue, Process
from time import sleep
from random import randint

# 创建消息队列
q = Queue(3)

def request():
    for i in range(20):
        x = randint(0, 100)
        y = randint(0, 100)
        q.put((x, y))

def handle():
    while True:
        sleep(0.5)
        try:
            x, y = q.get(timeout=3)
        except:
            break
        else:
            print("%d + %d = %d" % (x, y, x + y))


p1 = Process(target=request)
p2 = Process(target=handle)
p1.start()
p2.start()
p1.join()
p2.join()

共享内存

  1. 通信原理:在内存中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前的内容。
  2. 实现方法
from multiprocessing import Value,Array

obj = Value(ctype,data)
功能:开辟共享内存
参数:ctype 表示共享内存空间类型  'i'整型  'f'浮点  'c'字符(只能指定一个类型)
     data 共享内存空间初始数据
返回值:共享内存对象
 
obj.value  对该属性的修改或查看即对共享内存的读写

实现代码(存入一个值):

from multiprocessing import Process, Value
import time
import random

# 创建共享内存
money = Value('i', 5000)#存一个值


# 操作共享内存
def man():
    for i in range(30):
        time.sleep(0.2)
        money.value += random.randint(1, 1000)


def girl():
    for i in range(30):
        time.sleep(0.16)
        money.value -= random.randint(100, 900)


m = Process(target=man)
g = Process(target=girl)

m.start()
g.start()

m.join()
g.join()

print("一月余额:", money.value)
obj = Array(ctype,data)
功能:开辟共享内存空间
参数:ctype 表示共享内存数据类型
     data  整数则表示开辟空间的大小(存几个整数的列表),其他数据类型表示开辟空间存放的初始化数据
返回值:共享内存对象
 
Array共享内存读写:通过遍历obj可以得到的每个值,直接可以通过索引号修改任意值。

* 可以使用obj.value直接打印共享内存中的字节串

实现代码(存入多个值):

from multiprocessing import Process, Array

# shm = Array('i', 5)  # 初始化5个整型列表空间
shm = Array('i', [1, 2, 3])  # 初始化列表为[1,2,3]
# shm = Array('c',b'hello')  # 字节串

def fun():
    #  共享内存对象可迭代
    for i in shm:
        print(i)
    shm[1] = 1000  # 修改共享内存


p = Process(target=fun)
p.start()
p.join()

for i in shm:
    print(i, end=" ")  # [1, 1000, 3]

# 通过value属性访问字节串(只能字节串使用)
print(shm.value)

本地套接字

  1. 功能:用于本地两个程序之间进行数据的收发
  2. 套接字文件:用于本地套接字之间通信时,进行数据传输的介质。
  3. 创建本地套接字流程

【1】创建本地套接字

sockfd = socket(AF_UNIX,SOCK_STREAM)

【2】绑定本地套接字文件

sockfd.bind(file)

【3】监听,接收客户端连接,消息收发

listen()–>accept()–>recv(),send()

unix_recv(接收端):

from socket import *
import os

# 确定套接字文件
sock_file = './sock'

# 判断文件是否存在,存在就删除
if os.path.exists(sock_file):
    os.remove(sock_file)

# 创建本地套接字
sockfd = socket(AF_UNIX, SOCK_STREAM)

# 绑定套接字文件
sockfd.bind(sock_file)

# 监听,连接
sockfd.listen(3)
while True:
    c, addr = sockfd.accept()
    while True:
        data = c.recv(1024)
        if not data:
            break
        print(data.decode())
    c.close()
sockfd.close()

unix_send(发送端):

from socket import *

# 确保两边使用同一个套接字文件
sock_file = './sock'
sockfd = socket(AF_UNIX, SOCK_STREAM)
sockfd.connect(sock_file)

while True:
    msg = input(">>")
    if not msg:
        break
    sockfd.send(msg.encode())

sockfd.close()

信号量(信号灯集)

  1. 通信原理:给定一个数量对多个进程可见,多个进程都可以操作该数量增减,并根据数量值决定自己的行为。
  2. 实现方法:
from multiprocessing import Semaphore

sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象

sem.acquire()  将信号量减1 当信号量为0时阻塞
sem.release()  将信号量加1
sem.get_value() 获取信号量数量

实现代码:

from multiprocessing import Semaphore, Process
from time import sleep
import os

# 创建信号量
# 服务程序最多允许三个进程同时执行事件
sem = Semaphore(3)

def handle():
    print("%d 想执行事件" % os.getpid())
    # 想执行必须获取信号量
    sem.acquire()  # 减少信号量
    print("%d 开始执行操作" % os.getpid())
    sleep(3)
    print("%d 完成操作" % os.getpid())
    sem.release()  # 增加信号量

jobs = []
# 5个进程请求执行事件
for i in range(5):
    p = Process(target=handle)
    jobs.append(p)
    p.start()

for i in jobs:
    i.join()
    
print(sem.get_value())  # 获取信号量的值(3)

作业:
multiprocess创建两个进程,同时复制一个文件的上下两半部分,各自复制到一个新的文件里。

from multiprocessing import Process
import os

filename = './timg.jpeg'
# 获取图片大小
size = os.path.getsize(filename)


# 复制文件上半部分
def top():
    f = open(filename, 'rb')
    n = size // 2
    fw = open('top.jpeg', 'wb')
    fw.write(f.read(n))
    f.close()
    fw.close()


# 复制文件下半部分
def bot():
    f = open(filename, 'rb')
    fw = open('bot.jpeg', 'wb')
    f.seek(size // 2, 0)
    while True:
        data = f.read(1024)
        if not data:
            break
        fw.write(data)
    f.close()
    fw.close()


t = Process(target=top)
b = Process(target=bot)

t.start()
b.start()
t.join()
b.join()

注意:如果父进程中打开文件,创建进程通信对象或者创建套接字,子进程从父进程内存空间获取这些内容,那么父子进程对该对象的操作会有一定的属性关联。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值