最近在写一个项目,需要使用到多进程。由于整个网络服务器由自己开发,并没有使用模块,所以在多进程上面越用越多的疑惑。经过一系列的测试,对整个python多进程有了更多的认识。
每当使用multiprocessing创建新的进程,会复制主进程的所有状态和参数。所以此时调用主进程的全局变量是可以的,但是修改之后不会上传会主进程。
**
**
在新的主进程类里面创建新类的时候,如果要使用全局变量,则需要将主进程一个变量赋值为全局变量绑定地址之后,再将这个主进程的变量赋值给新类,就可以全局使用了。但是如果没有这么做,name就不会得到全局变量。子类修改值不会改到主类上面去。
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优先级运行
进程特征
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
字符串,表示退出时打印的内容
处理子进程退出状态
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()
可以在windows和linux上使用的进程方式。操作简单,主要有两种,继承多进程类和直接使用进程方法。
1、使用进程方法:
import multiprocessing as ms
def test():
pass
p1 = ms.Process(target=test) # 创建子进程
p1.start() # 子进程 开始执行
p1.join() # 等待子进程结束,linu
multiprocessing最大的优势在于windows不需要进行进程回收,但是在linux仍然需要考虑回收进程(比如在服务器上等长期运行的环境中),亲测start函数也会回收僵尸进程,这个模块六翻了。
继承Process来创建进程类:
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 则子进程会随着主进程结束而结束。
产生原因 : 如果有大量任务需要多进程完成,则可能需要频繁的创建删除进程,给进算计带来较多的资源消耗。
原理 : 创建适当的进程放入进程池,用来处理待处理事件,处理完毕后进程不销毁,仍然在进程池中等待处理其他事件。 进程的复用降低了资源的消耗
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: 上锁
…
…
解锁