一.创建进程的两种方式:
第一种创建方式:
from multiprocessing import Process
import time
def task(name):
print('%s is running' % name)
time.sleep(3)
print('%s is over'% name)
# 注意:在windows系统中,创建进程会将代码以模块的方式从头到尾加载一遍
# 一定要写在if __name__ == '__main__':代码块里
# 强调:函数名一旦加括号,执行优先级最高,立即执行
if __name__ == '__main__':
p1 = Process(target= task, args= ('egon',))
p1.start()
print('主')
第二种方式:
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super().__init__()
self.name = name
# 必须写run方法
def run(self):
print('%s is running'%self.name)
time.sleep(1)
print('%s is end'%self.name)
if __name__ == '__main__':
obj = MyProcess('egon')
obj.start()
print('主')
二.join方法
通过join方法使主进程等待子进程结束,才继续运行。
from multiprocessing import Process
import time
def task(name, n):
print('%s is running'% name)
time.sleep(1)
print('%s is over' % name)
if __name__ == '__main__':
start_time = time.time()
p_list = []
for i in range(3):
p = Process(target=task, args=('子进程%s'%i, i))
p.start()
p_list.append(p)
for i in p_list:
i.join()
print('主', time.time() - start_time)
# join 的作用仅仅只是让主进程等待子进程的结束,不会影响子进程的运行
三.进程对象的其他相关方法
from multiprocessing import Process, current_process
import time
import os
def task():
print('%s is runnng'% os.getpid()) # 获取子进程id
time.sleep(3)
print('%s is over'%os.getppid()) # 获取父进程id
if __name__ == '__main__':
p1 = Process(target=task)
p1.start()
print('主')
# p.terminate() # 杀死子进程
# time.sleep(0.1)
# print(p.is_alive()) # 判断子进程是否存活
四.进程间的数据隔离
# 进程与进程之间的数据是隔离的
from multiprocessing import Process
x = 100
def task():
global x
x = 1
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join()
print('主', x)
五.守护进程
守护进程随主进程的结束而结束。
主进程创建守护进程:
1.守护进程会在主进程代码执行结束后就终止。
2.守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
注意:进程之间互相独立,主进程代码运行结束,守护进程随即终止。
from multiprocessing import Process
import time
def task(name):
print('%s 正活着' % name)
time.sleep(2)
print('%s 正常死亡' % name)
if __name__ == '__main__':
p = Process(target=task, args=('egon总管',)) # 以元组形式
p.daemon = True # 必须在start()开启进程命令之前
p.start()
print('皇帝jason正在死亡')
六.互斥锁
为什么要使用互斥锁?
多个任务可以同时在几个进程中并发处理,他们之间的运行没有顺序,一旦开启也不受我们控制。
尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题:
当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。
锁一定要在主进程中创建,给子进程去用
解决多个进程操作同一份数据,造成数据不安全的情况
加锁会将并发变成串行
锁通常用在对数据操作的部分,并不是对进程全程加锁
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行的修改,速度是慢了,但牺牲了速度却保证了数据安全。
模拟抢票案例:
from multiprocessing import Process,Lock
import json
import time
import random
def search(i):
with open('info', 'r', encoding='utf-8') as f:
data= json.load(f)
print('用户查询余票数:%s' %data.get('ticket'))
def buy(i):
with open('info', 'r', encoding= 'utf-8')as f:
data = json.load(f)
time.sleep(random.randint(1,3))
if data.get('ticket') >0:
data['ticket'] -= 1
with open('info', 'w', encoding='utf-8')as f:
json.dump(data, f)
print('用户%s抢票成功'%i)
else:
print('用户%s查询余票为0'%i)
def run(i, mutex):
search(i)
mutex.acquire()
buy(i)
mutex.release()
if __name__ == '__main__':
mutex = Lock()
for i in range(10):
p = Process(target=run, args=(i, mutex))
p.start()
七.孤儿进程与僵尸进程
孤儿进程:如果父进程先退出,子进程还没退出那么子进程将被 托孤给init进程,这是子进程的父进程就是init进程。
僵尸进程:进程终止后进入僵死状态(zombie),等待告知父进程自己终止,后才能完全消失.但是如果一个进程已经终止了,但是其父进程还没有获取其状态,那么这个进程就称之为僵尸进程.僵尸进程还会消耗一定的系统资源,并且还保留一些概要信息供父进程查询子进程的状态可以提供父进程想要的信息.一旦父进程得到想要的信息,僵尸进程就会结束.
两种情况下会回收子进程的pid等信息
1.父进程正常结束
2.join方法
查看僵尸进程:ps aux |grep 'Z'
八.队列
队列是一种特殊的线性表,是一种先进先出(FIFO—first in first out)的数据结构,堆栈是先进后出。
队列相当于管道+锁,之前为了保证数据的安全性,给数据处理部分的代码加了互斥锁,但是牺牲了程序的运行效率,通过队列,可以保证数据的安全性,并且执行效率也不会受到影响。
6.1、队列的常用方法
Queue.put(item,block=True,timeout=None) 放数据
Queue.get(block=True,timeout=None) 取数据
Queue.qsize() 判断队列大小
Queue.empty() #return True if empty,判断队列是否为空,在并发的情况下,这个方法判断不准确!
Queue.full() #return True if full,判断队列是否已满
6.2、队列的三种:
先进先出class queue.Queue(maxsize=0) :
q = queue.Queue() #先进先出
q.put("d1")
q.put("d2")
q.put("d3")
print(q.qsize())
print(q.get()) #d1
print(q.get()) #d2
print(q.get()) #d3
# print(q.get()) # 如果队列为空,get会在原地等待队列中有新数据过来
#print(q.get_nowait()) # 在队列有数据的情况下,跟get取值一样,当队列没有数据的情况下,取值直接报错
#print(q.get(block=False)) #设置不阻塞
#print(q.get(timeout=1)) #设置超时时间
后进先出class queue.LifoQueue(maxsize=0) :
q = queue.LifoQueue() #后进先出
q.put("d1")
q.put("d2")
q.put("d3")
print(q.qsize())
print(q.get()) #d3
print(q.get()) #d2
print(q.get()) #d1
class queue.PriorityQueue(maxsize=0) 存储数据时可设置优先级的队列:
import queue
q = queue.PriorityQueue() #可设置优先级
q.put((-1,"aaa"))
q.put((-3,"bbb"))
q.put((10,"shj"))
q.put((6,"ccc"))
print(q.get())
print(q.get())
print(q.get())
print(q.get())
'''
输出:
(-3, 'bbb')
(-1, 'aaa')
(6, 'ccc')
(10, 'shj')
八.生产者与消费者模型
1. 什么是生产者消费者模型
生产者:代指生产数据的任务
消费者:代指处理数据的任务
该模型的工作方式:
生产者生产数据传递消费者处理
实现方式:
生产者生产数据放到---->队列<------消费者从队列拿数据
2. 为何要用
当程序中出现明细的两类任务,一类负责生产数据,一类负责处理数据
就可以引入生产者消费者模型来实现生产者与消费者的解耦合,平衡生产能力与消费能力,从提升效率
Queue:管道+锁
'''
生产者消费者模型:
生产者:做包子的,生产数据的
消费者:买包子的,处理数据
解决供需不平衡问题
定义一个队列,用来存放固定数量的数据
解决一个生产者和消费者不需要直接打交道,两者通过队列实现数据传输
Queue:管道 + 锁
'''
from multiprocessing import Queue,Process,JoinableQueue
import time
import random
def producer(name, food, q):
for i in range(5):
data = '%s生产了%s%s' %(name, food, i)
time.sleep(random.randint(1, 3))
print(data)
q.put(data) # 将生产的数据放入队列中
def consumer(name, q):
while True:
data = q.get()
if data is None:break
time.sleep(random.randint(1,3))
print('%s吃了%s'%(name, data))
q.task_done() # 告诉队列你已经将数据取出并处理完毕
if __name__ == '__main__':
q = JoinableQueue() # 生成一个队列对象
p1 = Process(target=producer, args=('大厨egon', '包子', q))
p2 = Process(target=producer, args=('靓仔tank', '生蚝', q))
c1 = Process(target= consumer, args=('吃货owen', q))
c2 = Process(target= consumer, args=('坑货kiven', q))
p1.start()
p2.start()
c1.daemon = True
c2.daemon = True
c1.start()
c2.start()
# 等待生产者生产完所有的数据
p1.join()
p2.join()
# 在生产完数据之后,往队列里放一个提示性消息,告诉消费者没有数据了
# q.put(None)
# q.put(None)
q.join() # 等待队列中的数据全部取出
print('主')