python3多进程多种使用情况下详解

序言

最近在写一个项目,需要使用到多进程。由于整个网络服务器由自己开发,并没有使用模块,所以在多进程上面越用越多的疑惑。经过一系列的测试,对整个python多进程有了更多的认识。

最大体会

每当使用multiprocessing创建新的进程,会复制主进程的所有状态和参数。所以此时调用主进程的全局变量是可以的,但是修改之后不会上传会主进程。
**

Process进程创建时,子进程会将主进程的Process对象完全复制一份。

**
在新的主进程类里面创建新类的时候,如果要使用全局变量,则需要将主进程一个变量赋值为全局变量绑定地址之后,再将这个主进程的变量赋值给新类,就可以全局使用了。但是如果没有这么做,name就不会得到全局变量。子类修改值不会改到主类上面去。

有关linux进程的操作指令

cpu时间片
如果一个进程占有计算机核心,我们称为改进程占有计算机cpu时间片。

  • 多个任务之间是争夺cpu的关系
  • 谁占有cpu最终是操作系统决定

PCB (进程控制块)
在内存中开辟的一块空间,用来记录进程的信息,进程控制块是操作系统查找识别进程的标志。

进程信息 : ps -aux
PID(process ID) : 在操作系统中每个进程都有一个唯一的ID号用来区别于其他进程。ID号由操作系统自动分配,是一个大于0的整数

父子进程 : 在系统中除了初始化进程,每一个进程都有一个父进程,可能有0个或者多个子进程。由此形成父子进程关系。

查看进程树 : pstree
查看父进程PID: ps -ajx
ps -aux —> STAT
S 等待态 (可中断等待)
D 等待态 (不可中断等待)
T 等待态 (暂停状态)
R 运行态 (包含就绪态)
Z 僵尸进程

< 高优先级进程
N 优先级较低
l 有子进程的
s 会话组组长

  • 前台进程

进程优先级
查看进程优先级
top 动态查看系统中的进程信息, 用<>翻页
取值范围 -20 – 19 -20优先级最高

使用指定的优先级运行程序
nice : 指定运行的优先级
e.g. nice -9 ./while.py 以优先级9运行
nice --9 ./while.py 以-9优先级运行

进程特征

  1. 进程之间运行互不影响 各自独立运行
  2. 进程是操作系统资源分配的最小单位
  3. 每个进程空间独立,各自占有一定的虚拟内存

fork

fork是一种只能在linux系统使用的基础的多进程操作,操作比较简单,但是不易管理。在只创建单个多进程时很方便,多次创建进程会导致进程难以管理。优点是高效率,缺点是难操作。目前想在高并发情况下可能需要使用

import os 
from time import sleep

print("*******************")
a = 1

pid = os.fork()

if pid < 0:
    print("创建进程失败")
elif pid == 0:
    print("这是新的进程,新的进程操作在这里进行。进程结束之后一定要回收进程!!!")
    print("a = ",a)
    a = 10000
else:
    sleep(1)
    print("这是原有进程,原有的进程在这里继续")
    print("parent a =",a)

print("演示完毕") 

获取进程PID
os.getpid()
功能 : 获取当前进程的进程号
返回值 : 返回进程号

os.getppid()
功能 : 获取当前进程父进程的PID号
返回值 : 返回进程号

进程退出

os._exit(status)
功能 : 进程退出
参数 : 进程的退出状态

sys.exit([status])
功能 : 进程退出
参数 : 数字表示退出状态,不写默认为0
字符串,表示退出时打印的内容

  • sys.exit 可以通过捕获 SystemExit异常阻止退出

如何避免僵尸进程产生

处理子进程退出状态
pid,status = os.wait()
功能 :在父进程中阻塞等待处理子进程退出
返回值: pid 退出的子进程的PID号
status 获取子进程退出状态

pid,status = os.waitpid(pid,option)
功能 :在父进程中阻塞等待处理子进程退出
参数 : pid -1 表示等待任意子进程退出
>0 表示等待对应PID号的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞
返回值: pid 退出的子进程的PID号
status 获取子进程退出状态

waitpid(-1,0) ===> wait()

  • 让父进程先退出
    1. 父进程创建子进程等待子进程退出
    2. 子进程创建二级子进程后立即退出
    3. 二级子进程称为孤儿,和原来的父进程各自执行 事件

multiprocessing

可以在windows和linux上使用的进程方式。操作简单,主要有两种,继承多进程类和直接使用进程方法。

1、使用进程方法:

import multiprocessing as ms
def test():
    pass
p1 = ms.Process(target=test) # 创建子进程
p1.start() # 子进程 开始执行
p1.join() # 等待子进程结束,linu
  • 使用multiprocessing创建子进程,同样子进程复制父进程的全部代码段,父子进程各自执行互不影响,父子进程有各自的运行空间。
  • 当子进程执行完毕后,会产生一个僵尸进程,其会被join函数回收,或者再有一条进程开启,start函数也会回收僵尸进程,所以不一定需要写join函数。
  • windows系统在子进程结束后会立即自动清除子进程的Process对象,而linux系统子进程的Process对象,如果没有join函数和start函数的话会在主进程结束后统一清除。

multiprocessing最大的优势在于windows不需要进行进程回收,但是在linux仍然需要考虑回收进程(比如在服务器上等长期运行的环境中),亲测start函数也会回收僵尸进程,这个模块六翻了
继承Process来创建进程类:

  1. 继承Process
  2. 编写自己的__init__ ,同时加载父类init方法
  3. 重写run方法,可以通过生成的对象调用start自动运行
from multiprocessing import Process
import os
import time
class MyProcess(Process):
    #重新init方法
    def __init__(self,interval):
        #下面一句是调用父类init方法,这一本尽量不要少,因为父类还有很多事情需要在init方法内处理
        Process.__init__(self)
        self.interval=interval

    #重写run方法
    def run(self):
        print("子进程运行中,pid=%d,父进程:%d" % (os.getpid(), os.getppid()))
        t_start=time.time()
        time.sleep(self.interval)
        t_end=time.time()
        print("子进程运行结束,耗时:%0.2f秒"%(t_end-t_start))

if __name__=="__main__":
    t_start=time.time()
    print("父进程开始执行")
    p=MyProcess(2)
    p.start()
    p.join()
    t_end=time.time()
    print("父进程运行结束,耗时:%0.2f秒" % (t_end - t_start))

Process其他方法:

p.is_alive() 判断进程生命周期状态,处于生命周期得到True否则返回False
p.name 进程名称 默认为Process-1
p.pid 进程的PID号
p.daemon
默认状态False 主进程退出不会影响子进程执行,如果设置为True 则子进程会随着主进程结束而结束。

  • 要在start前设置
  • 一般不和join一起使用

进程池(Pool)

产生原因 : 如果有大量任务需要多进程完成,则可能需要频繁的创建删除进程,给进算计带来较多的资源消耗。
原理 : 创建适当的进程放入进程池,用来处理待处理事件,处理完毕后进程不销毁,仍然在进程池中等待处理其他事件。 进程的复用降低了资源的消耗

Pool(processes)
功能 : 创建进程池对象
参数 :表示进程池中有多少进程

pool.apply_async(func,args,kwds)
功能 : 将事件放入到进程池队列
参数 : func 事件函数,args 以元组形式给func传参,kwds 以字典形式给func传参
返回值 : 返回一个代表进程池事件的对象

pool.apply(func,args,kwds)
功能 : 将事件放入到进程池队列
参数 : func 事件函数,args 以元组形式给func传参,kwds 以字典形式给func传参

pool.close()
功能: 关闭进程池

pool.join()
功能:回收进程池

pool.map(func,iter)
功能: 将要做的时间放入进程池
参数: func 要执行的函数 iter 迭代对象
返回值 : 返回事件函数的返回值列表

from multiprocessing import Pool 
from time import sleep,ctime 

def worker(msg):
    sleep(2)
    print(msg)
    return ctime()

#创建进程池
pool = Pool(processes = 4)

result = []
for i in range(10):
    msg = "hello %d"%i
    #将事件放入进程池队列,等待执行
    r = pool.apply_async(func = worker,args = (msg,))
    result.append(r)

#关闭进程池
pool.close()

#回收
pool.join()

for i in result:
    print(i.get())  #获取事件函数的返回值

进程通信

进程间通信 (IPC)

原因 : 进程空间相对独立,资源无法相互获取,此时在不同进程间通信需要专门方法。

进程间通信方法 : 管道 消息队列 共享内存 信号
信号量 套接字

管道通信 Pipe

通信原理 : 在内存中开辟管道空间,生成管道操作对象,多个进程使用"同一个"管道对象进行操作即可实现通信

multiprocessing —》 Pipe

fd1,fd2 = Pipe(duplex = True)
功能 : 创建管道
参数 : 默认表示双向管道, 如果设置为False则为单向管道
返回值 : 表示管道的两端,如果是双向管道 都可以读写,如果是单向管道 则fd1只读 fd2只写

fd.recv()
功能 : 从管道读取信息,返回值: 读取到的内容, 如果管道为空则阻塞

fd.send(data)
功能:向管道写入内容,参数: 要写入的内容,可以发送python数据类型

消息队列
队列 : 先进先出
通信原理 : 在内存中建立队列数据结构模型。多个进程都可以通过队列存入内容,取出内容的顺序和存入顺序保持一致

创建队列
q = Queue(maxsize = 0)
功能 : 创建消息队列
参数 : 表示最多存放多少消息。默认表示根据内存分配存 储
返回值 : 队列对象

q.put(data,[block,timeout])
功能: 向队列存储消息
参数 :data 要存的内容,block 默认队列满时会阻塞,设置为False则非阻塞,timeout 超时时间

data = q.get([block,timeout])
功能:获取队列消息
参数:block 默认队列空时会阻塞,设置为False则非阻塞, timeout 超时时间
返回值 : 返回取出的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 判断队列中消息数量
q.close() 关闭队列
共享内存
通信原理:在内存空开辟一块空间,对多个进程可见,进程可以写入输入,但是每次写入的内容会覆盖之前的内容。

obj = Value(ctype,obj)
功能 : 开辟共享内存空间
参数 : ctype 要存储的数据类型,obj 共享内存的初始化数据
返回 :共享内存对象

obj.value 即为共享内存值,对其修改即修改共享内存

obj = Array(ctype,obj)
功能 : 开辟共享内存空间
参数 : ctype 要存储的数据格式,obj 初始化存入的内容 比如列表,字符串,如果是整数则表示开辟空间的个数
返回值 : 返回共享内存对象
* 可以通过遍历过户每个元素的值
e.g. [1,2,3] —> obj[1] == 2
* 如果存入的是字符串
obj.value 表示字符串的首地址

     管道         消息队列       共享内存

开辟空间 内存 内存 内存

读写方式 两端读写 先进先出 覆盖之前内容
双向/单向
效率 一般 一般 较高
应用 多用于父 广泛灵活 需要注意
子进程 进行互斥操作

信号通信
一个进程向另一个进程发送一个信号来传递某种讯息,接受者根据接收到的信号进行相应的行为
kill -l 查看系统信号
kill -sig PID 向一个进程发送信号

关于信号
信号名称 信号含义 默认处理方法

SIGHUP 连接断开
SIGINT CTRU-C
SIGQUIT CTRU-
SIGTSTP CTRL-Z
SIGKILL 终止一个进程
SIGSTOP 暂停一个进程
SIGALRM 时钟信号
SIGCHLD 子进程状态改变时给父进程发出

python 发送信号
signal
os.kill(pid,sig)
功能: 发送信号
参数: pid 目标进程,sig 要发送的信号

import signal

signal.alarm(sec)
功能 : 向自身发送时钟信号 --》 SIGALRM
参数 : sec 时钟时间

  • 进程中只能有一个时钟,第二个会覆盖第一个时间

同步执行 : 按照顺序逐句执行,一步完成再做下一步
异步执行 : 在执行过程中利用内核记录延迟发生或者准备 处理的事件。这样不影响应用层的持续执行。 当事件发生时再由内核告知应用层处理

  • 信号是唯一的异步通信方法

signal.pause()
功能:阻塞等待接收一个信号

signal.signal(signum,handler)
功能: 处理信号
参数: signum 要处理的信号
handler 信号的处理方法
SIG_DFL 表示使用默认的方法处理
SIG_IGN 表示忽略这个信号
func 传入一个函数表示用指定函数处理
def func(sig,frame)
sig: 捕获到的信号
frame : 信号对象

信号量(信号灯)

原理 : 给定一个数量,对多个进程可见,且多个进程都可以操作。进程通过对数量多少的判断执行各自的行为。
multiprocessing --》 Semaphore()
sem = Semaphore(num)
功能: 创建信号量,参数: 信号量初始值,返回: 信号量对象
sem.get_value() 获取信号量值
sem.acquire() 将信号量减1 当信号量为0会阻塞
sem.release() 将信号量加1

进程的同步互斥

临界资源 :多个进程或者线程都能够操作的共享资源
临界区 : 操作临界资源的代码段
同步 : 同步是一种合作关系,为完成某个任务,多进程或者多线程之间形成一种协调,按照约定或条件执行操作临界资源。
互斥 : 互斥是一种制约关系,当一个进程或者线程使用临界资源时进行上锁处理,当另一个进程使用时会阻塞等待,直到解锁后才能继续使用。

Event 事件
multiprocessing --》 Event
创建事件对象
e = Event()
设置事件阻塞
e.wait([timeout])
事件设置 当事件被设置后e.wait()不再阻塞
e.set()

清除设置 当事件设置被clear后 e.wait又会阻塞
e.clear()
事件状态判断
e.is_set()

Lock 锁
创建对象
lock = Lock()
lock.acquire() 上锁 如果锁已经是上锁状态调用此函数会阻塞
lock.release() 解锁
with lock: 上锁


解锁

猜你喜欢

转载自blog.csdn.net/weixin_36179862/article/details/84305143
今日推荐