活动地址:CSDN21天学习挑战赛
**
**
程序,比如xxx.py是一个程序,是静态文件,占用的资源是文件本身的大小。
进程,一个程序运行起来后,代码+用到的资源称为进程,它是操作系统分配资源的基本单元。不仅可以通过线程完成多任务,进程也是可以的。
进程的状态
工作中,任务数往往是大于CPU的核数,即一定有一些任务正在执行,而另外一些任务在等待CPU进行执行,因此导致有了不同的状态。
multiprocessing模块通过创建Process对象,然后调用start()方法来生成进程,Process与threading.Thread的API相同。
multiprocessing.Process类的语法格式:
multiprocessing.Process(
group=None,
target=None,
name=None,
args=(),
kwargs={},
*,
daemon=None)
multiprocessing.Process类有如下方法和属性
方法名/属性 | 说明 |
---|---|
run() | 进程具体执行的方法 |
start() | 启动子进程实例,即创建子进程 |
join([timeout]) | timeout=None,阻塞至调用join()方法的进程终止;如果timeout是一个正数,则最多会阻塞timeout秒 |
is_alive() | 判断进程/子进程是否还在活着 |
terminate() | 不管任务是否完成,立即终止子进程 |
kill() | 与terminate()相同,在UNIX上使用SIGKILL信号 |
close() | 关闭Process对象,释放与之关联的所有资源 |
name | 当前进程的别名,默认为Process-N,N为从1开始递增的整数 |
pid | 当前进程的pid,进程号 |
exitcode | 子进程的退出代码 |
daemon | 进程的守护标志,是一个布尔值 |
authkey | 进程的身份验证密钥 |
sentinel | 系统对象的数字句柄,当进程结束时将变为ready |
创建子进程时,需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动。
# 子进程运行实例
from multiprocessing import Process
import time
def run_proc():
'''
子进程要执行的代码
'''
while True:
print("---- 2 ---")
time.sleep(1)
if __name__ == "__main__":
p = Process(target=run_proc)
p.start()
while True:
print("--- 1 ---")
time.sleep(1)
使用os模块的getpid()获取当前进程的进程号
import os
os.getpid()
使用示例:
# -*- coding:utf-8 -*-
from multiprocessing import Process
import os
import time
def run_proc():
print("子进程运行,pid = %d" % os.getpid())
print("子进程将要结束")
time.sleep(1)
if __name__ == "__main__":
print("父进程执行中,父进程pid = %d" % os.getpid())
p = Process(target=run_proc)
p.start()
time.sleep(1)
在创建进程时,设置参数args和kwargs可以实现创建子进程时给特定的函数传递参数。
使用示例:
# 创建子进程时给指定的函数传递参数
from multiprocessing import Process
import os
import time
def run_proc(name,age,**kwargs):
for i in range(10):
print("子进程运行中--%d--,name=%s, age=%d, pid=%d" % (i,name,age,os.getpid()))
print(kwargs)
time.sleep(0.3)
if __name__ == "__main__":
p = Process(target=run_proc,args=("tom",12),kwargs={"jerry":10})
p.start()
time.sleep(1)
p.terminate() # 1s后,终止子进程
p.join()
from multiprocessing import Process
import os
import time
# 全局变量
nums = [11,22]
# 子进程1
def work1():
print("in process1 pid = %d, nums = %s" % (os.getpid(),nums))
for i in range(3):
nums.append(i)
time.sleep(1)
print("in process1 pid = %d, nums = %s" % (os.getpid(),nums))
# 子进程2
def work2():
print("in process2 pid = %d, nums = %s" % (os.getpid(),nums))
if __name__ == "__main__":
p1 = Process(target=work1)
p1.start()
p1.join()
p2 = Process(target=work2)
p2.start()
显示结果,可以看出来全局变量nums在work1中改变,但在work2显示的仍然是初始的值。
进程之间有时需要通信,操作系统提供了很多机制来实现进程间的通信,如Queue。
方法名 | 说明 |
---|---|
q = Queue() | 构造函数,初始化Queue()对象 ,如果括号中没有指定最大可接受消息数量,或数量为负,表示可接受的消息数量没有上限直至内存耗尽 |
Queue.qsize() | 返回当前队列包含的消息数量 |
Queue.empty() | 如果队列为空,返回True,反之False |
Queue.full() | 如果队列满了,返回True,反之False |
Queue.get([block[,timeout]]) | 获取队列中的一条消息,然后将其从列队中移除,block默认为True。有两种情况:1. 如果block使用默认值,且没有设置timeout(单位秒),消息列队如果为空,此时程序将被阻塞(停在读取状态),知道从消息队列读取到消息为止;如果设置了timeout,会等待timeout秒,如果还没有读取到任何消息,抛出Queue.Empty异常。2. 如果block为False,消息队列如果为空,会立刻抛出Queue.Empty异常。 |
Queue.get_nowait() | 相当与Queue.get(False) |
Queue.put(item[,block[,timeout]]) | 将item消息写入到队列中,block默认值为True。有两种情况:1. 如果block为默认值,且没有设置timeout(单位秒),消息队列如果已没有空间写入,此时程序将被阻塞(停在写入状态),直到消息队列腾出空间为止;如果设置了timeout,会等待timeout秒,如果还没有空间,抛出Queue.Full异常。2. 如果block为False,消息队列如果没有空间可写入,会立刻抛出Queue.Full异常。 |
Queue.put_nowait(item) | 相当于Queue.put(item,False) |
from multiprocessing import Queue
import time
# 初始化Queue对象,最多可以接收3条信息
q = Queue(3)
try:
# 将消息写入队列中
q.put("消息1")
q.put("消息2")
# 检查消息队列是否已满
print("%s\t消息队列满了吗?:%s" % (time.ctime(),q.full()))
# 将消息3写入队列
q.put("消息3")
# 检查消息队列是否已满
print("%s\t消息队列满了吗?:%s" % (time.ctime(),q.full()))
# 队列中插入消息4
q.put("消息4",block=True,timeout=2)
except:
print("%s\t消息队列已满,现有消息数量:%d" % (time.ctime(),q.qsize()))
try:
q.put_nowait("消息4")
except:
print("%s\t消息队列已满,现有消息数量:%d" % (time.ctime(),q.qsize()))
# 如果消息队列不空,插入消息
if not q.full():
q.put("消息4")
# 读取消息时,先判断消息是不是空
if not q.empty():
for i in range(q.qsize()):
print(q.get_nowait())
# 在父进程中创建两个子进程,一个写数据,一个读数据
from multiprocessing import Queue,Process
import time,os,random
# 写数据
def write(q:Queue):
for val in ['A','B','C']:
q.put(val)
time.sleep(random.random())
# 读数据
def read(q:Queue):
while True:
if not q.empty():
val = q.get(True)
print("Get %s from queue, current queue has %d msgs" %(val,q.qsize()))
time.sleep(random.random())
else:
break
if __name__ == "__main__":
q = Queue()
pw = Process(target=write,args=(q,))
pr = Process(target=read,args=(q,))
pw.start()
pw.join()
pr.start()
pr.join()
print("所有数据都写入并读完")
锁是为了确保数据一致性。比如读写锁,每个进程给一个变量增加1,但如果在一个进程读取后还没有写入前,另一个进程读取了该值并写入该值,那么前一个进程写入的数据就是错误的,这时需要加锁使该值保持一致性。
可以通过multiprocession.Lock类控制一段代码在同一时间只能被一个进程执行。Lock类的两个方法:
acquire()用来获取锁;release()用来释放锁。
如果锁的状态是unlocked,当一个进程调用acquire(),会立即修改为locked()并返回,该进程获得锁;如果锁的状态是locked,当一个进程调用acquire(),该进程会阻塞。
Lock的使用:
当没有加锁时
import multiprocessing
import time,os
def add(num,value):
print("current pid = {0},add{1}:num = {2}".format(os.getpid(),value,num))
for i in range(3):
num += value
print("current pid = {0},add{1}:num = {2}".format(os.getpid(), value, num))
time.sleep(1)
if __name__ == "__main__":
num=0
p1 = multiprocessing.Process(target=add, args=(num, 1))
p2 = multiprocessing.Process(target=add, args=(num, 2))
p1.start()
p2.start()
print("proc end")
显示结果,进程执行时没有顺序,交替运行
当加锁时,其中一个进程执行完成后,另一个进程才会去执行,谁先抢到锁谁先执行
import multiprocessing
import time,os
def add(num, value, lock):
try:
lock.acquire()
print("current pid = {0},add{1}:num = {2}".format(os.getpid(),value,num))
for i in range(3):
num += value
print("current pid = {0},add{1}:num = {2}".format(os.getpid(), value, num))
time.sleep(1)
except Exception as err:
raise err
finally:
lock.release()
if __name__ == "__main__":
lock = multiprocessing.Lock()
num=0
p1 = multiprocessing.Process(target=add, args=(num, 1, lock))
p2 = multiprocessing.Process(target=add, args=(num, 2, lock))
p1.start()
p2.start()
print("proc end")
当需要创建的子进程数量不多时,可以使用multiprocessing的Process类动态生成多个进程,但是如果需要的子进程特比多,成百上千个时,手动创建就不太现实,此时可以使用multiprocessing模块的Pool类。
语法格式:
multiprocessing.pool.Pool([processes[,initializer[,initargs[,maxtasksperchild[,context]]]]])
参数说明:
processes:工作进程数目,如果processes为None,则使用os.cpu_count()返回的值;
initializer:如果initializer不为None,则每个进程会在启动时调用initializer(*initargs);
maxtasksperchild:一个工作进程在它退出或被一个新的工作进程代替之前能完成的任务数量,为了释放未使用的资源;
context:用于指定启动的工作进程的上下文。
有两种方式向进程池提交任务:
multiprocessing.Pool常用的函数:
方法名 | 说明 |
---|---|
close() | 关闭Pool,使其不再接受新的任务 |
terminate() | 不管任务是否完成,立即终止 |
join() | 主进程阻塞,等待子进程的退出,必须在close()或terminate()之后使用 |
初始化Pool时,可以指定最大进程数,
当有新的请求提交到Pool中,如果池还没有满,那么就会创建一个新的进程来执行该请求;
如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新任务。
线程池使用示例:
from multiprocessing import Pool
import os,time,random
def worker(msg):
t_start = time.time()
print("{0} start run, pid = {1}".format(msg,os.getpid()))
time.sleep(random.random()*2) # random() 随机生成0~1之间的浮点数
t_stop = time.time()
print("%d stop, use time:%.2f" % (msg,t_stop-t_start))
if __name__ == "__main__":
po = Pool(3) # 定义进程池,参数3表示最大进程数为3
for i in range(10):
# allpy_sync(要调用的目标,传递给目标的参数元组)
po.apply_async(worker, args=(i,)) # 每次循环会用空闲的子进程去调用目标
print("----start-----")
po.close() # 关闭线程池后,po不再接受新的请求
po.join() # 等待线程池po中的所有子进程执行完毕,
print("----stop-------")
如果要使用Pool创建进程,就需要使用multiprocessing.Manager中的Queue()。
使用示例:
from multiprocessing import Pool,Manager
import os,time,random
def reader(q):
'''
读取消息队列q中的消息
:param q: 消息队列
:return: 无
'''
print(f"reader start(pid={os.getpid()}), father pid = {os.getppid()}.")
for i in range(q.qsize()):
print(f"reade msg from Queue is : {q.get(True)}")
def writer(q):
'''
向消息队列中写入消息
:param q: 消息队列
:return: 无
'''
print(f"writer start(pid={os.getpid()}, father pid={os.getppid()}.")
for i in 'itcast':
print("write %c"%i)
q.put(i)
if __name__ == "__main__":
print(f"Process pid={os.getpid()} start.")
q=Manager().Queue()
po = Pool()
po.apply_async(writer,args=(q,)) # 向进程池提交任务
time.sleep(random.random())
po.apply_async(reader,args=(q,))
po.close() # 不再接受新的请求
po.join() # 阻塞至进程池中的任务完成
print(f"Process pi={os.getpid()} end!")
显示结果:
进程和线程的区别:
线程执行开销小,但不利于资源的管理和保护;
进程执行开销大,但利于资源的管理和保护。
全文参考:https://blog.csdn.net/yuan2019035055/article/details/124780526