python并发编程之多进程
1、操作系统
位于硬件与应用软件之间,本质也是一种软件,由系统内核和系统接口组成
和进程之间的关系是:
进程只能由操作系统创建
和普通软件区别:
操作系统是真正的控制硬件
应用程序实际在调用操作系统提供的接口
主要功能:
1. 封装了繁琐的硬件操作,为应用程序提供简单的接口
2.将应用程序对硬件的竞争变成有序的使用
2、多道技术
为了解决同一时间只有一个应用程序被执行问题
方法:切换+保存状态
切换:IO阻塞;执行时间过长;出现一个优先级更高的任务
保存状态:在任务切换前保存运行状态,以便后期恢复执行
实现原理:
空间复用:同一时间在内存中存放多个应用程序
时间复用:多个进程占用CPU同一时间片在不同的进程中相互切换, 遇到IO操作或占用CPU时间过长时,保存当前进程状态,切换到其他进程
强调:
多个进程之间相互独立,并是物理层面的隔离,以保证安全性
好处:
同一时间可以多个应用程序在执行,实现并发,在IO操作比较多时,极大的提高效率
弊端:
如果没有IO操作,切换反而会降低效率
应用程序的执行取决于IO操作,IO操作越多则效率越低
提高效率: 减小IO操作,避免被操作系统切换
3、进程与程序
进程:正在运行的程序,是一系列程序执行过程的总称
程序:一段代码,程序员将自己思维逻辑按照某种编程语言规范编写下来的一堆字符串
进程是由程序产生的,没有程序就没有进程
4、进程的三种状态

并行、并发、串行、阻塞
串行:按照固定顺序执行, 第一行执行完毕后才会执行第二行
需要注意的是 串行执行的过程中 因为计算操作太多也会导致程序卡住,但是这与io阻塞是不同的
并发:多进程同时运行,(单核+多道技术)
并行:多进程同时执行(多核才能实现)
阻塞:进程的状态。进程执行时遇到IO操作。CPU切换到其他进程
5、PID和PPID
import os
os.getpid(), 当前进程id
os.getppid(),当前进程的父进程id
6、进程的层次结构
无论是unix还是windows,进程只有一个父进程,不同的是:
1. 在unix所有的进程,都是以init进程为根,成树形结构。
2. 在window中,没有进程层次的概念,所有进程的地位相同,在创建进程时,父进程会得到一个特殊令牌(句柄),该句柄可以控制子进程,但父进程可以把该句柄传给其他子进程。
7、python中实现多进程
7.1 实现的两种方式
print(name)
p.start()
print("主")
from multiprocessing import Process
def __init__(self, name):
super(MyProcess, self).__init__()
self.name = name
print(self.name)
p = MyProcess('sun')
p.start()
7.2 进程之间的内存相互隔离
import time
x=1000
def task():
time.sleep(3)
global x
x=0
print('子进程',x)
print('main', x)
p=Process(target=task)
p.start()
print(x)
输出结果:
main 1000
子进程 0
1000
7.3 进程的方法和属性
start(),向操作系统发送一个开子进程信号
join(),使主进程等待子进程执行完毕后才会执行,本质是内部修改子进程优先级高于主进程
terminate(), 向操作系统发送一个终止子进程信号,发完之后继续执行
is_alive(), 获取进程的存活状态
p.name, 在创建时可以给进程取名字
p.exitcode, 获取进程的退出码 只有进程完全结束后才能获取到
p.pid # 获取进程的pid
8、 守护进程
指的是一个进程守护另一个进程。
如果把子进程变成守护进程则:
1. 守护进程会在主进程结束后就终止(主进程代码执行结束后会等待其他子线程结束后结束)
2. 守护进程内无法开启子进程。否则抛出异常AssertionError
from multiprocessing import Process import time def task(): print("subprocess is running.....") time.sleep(5) print("subprocess is down....") if __name__ == '__main__': p = Process(target=task, ) p.daemon = True print('main going....') p.start() time.sleep(1) print("exit....")
9、进程的安全问题
问题:多个进程并发去操作同一资源,由于是竞争关系就很容易出现错乱问题
解决方案:加锁,在访问共享资源前加锁,访问完毕后一定要解锁
使用工具:互斥锁
注意:
不能多次执行acquire,一次acquire对应一次release
acquire是一个阻塞函数。会一直等到锁被释放后才会继续执行
和join的区别:加锁之后,并发变串行,和join不同的是,没有规定顺序,谁先抢到谁执行;并且join是整个任务串行,锁仅仅是部分代码串行。
from multiprocessing import Process, Lock import time def task1(lock): lock.acquire() print("task1 is running.....") time.sleep(1) print('task1 is over.....') lock.release() def task2(lock): lock.acquire() print("task2 is running.....") time.sleep(1) print('task2 is over.....') lock.release() if __name__ == '__main__': lock = Lock() p1 = Process(target=task1, args=(lock,)) p2 = Process(target=task2, args=(lock,)) p1.start() p2.start() print('main over')
import json from multiprocessing import Process,Lock import time def search(name): with open("data", "rt") as f: data = json.load(f) print("%s查看剩余票数:%s"%(name, data['num'])) def get(name): time.sleep(0.5) with open('data', 'rt') as f: data = json.load(f) if data['num'] > 0: data['num'] -= 1 with open('data', 'wt') as f: json.dump(data, f, indent=4) print("%s成功购买到票"%name) else: print("票卖完了,请%s等候"%name) def task(name, lock): search(name) lock.acquire() get(name) lock.release() if __name__ == '__main__': users = ['aaa', 'bbb', 'ccc'] lock = Lock() for user in users: p = Process(target=task, args=(user, lock)) p.start()
10、进程之间的通讯IPC
进程之间内存是相互隔离的无法直接通讯
四种方式(间接通讯):
1. 使用一个共享文件,在硬盘创建一个文件。不同进程之间共享这个文件
优点:交换的数据量几乎没有限制
缺点:速度慢
2. 系统开辟一块共享内存 以供进程之间交换数据
优点:速度快
缺点:数据量不能太大
3. 管道
优点:封装文件的打开,关闭等操作
缺点:速度慢,并且单向的,编程复杂度较高
4. socket 不仅可以用于与远程计算机中的进程通讯,还可以用于与本地进程通讯
基于内存 速度快
from multiprocessing import Process, Manager, Lock def task(dic): dic['a'] += 1 print('子进程对a进行加法%s'%dic['a']) if __name__ == '__main__': m = Manager() dic = m.dict({'a': 10}) print("计算前a的值%s"%dic['a']) p = Process(target=task, args=(dic,)) p.start() p.join() print("计算后a的值%s"%dic['a'])
# 注意:如果有多个进程需要对共享内存操作,需要加上互斥锁,保证数据的安全性
11、队列
特点:先进先出
创建: from multiprocessing import Queue
q = Queue(maxsize) #maxsize表示队列允许的最大项数
方法:
q.put(block, timeout), 插入数据到队列,block=True,表示队列已满时阻塞等待插入数据, timeout表示阻塞等带的时间,超时抛出异常。block=False,表示队列已满时,不阻塞等待,直接抛出异常 q.get(block, timeout),取出数据,参数同上 q.get_nowait() = q.get(False) q.put_nowait() = q.put(False) q.empty(),q为空返回True,该结果不可靠,比如返回True时,又加入了新的数据 q.full(),满,不可靠 q.qsize(),个数,不可靠
12、生产者消费者模型
问题:由于生产者和消费者能力不匹配,导致双方可能相互等待,造成了效率降低
解决方法:
使用一个容器隔离生产者和消费者,生产者把生产的产品放到容器内,消费者直接从容器内取商品。
这样两者就不用相互等待,平衡了生产者和消费者之间的能力差异 提高处理效率,降低了双方耦合度,
实现方式:可以使用队列实现生产者消费者模型
from multiprocessing import Process, Queue import time import random def producer(q): """生产者生产商品""" for i in range(1,11): time.sleep(random.randint(1,3)) produce = "baozi%s"%i q.put(produce) print('生产者蒸了一个第%s个包子'%i) def consumer(q): """消费者消费商品""" for i in range(10): food = q.get() time.sleep(random.randint(1,3)) print("消费者吃了一个包子:%s"%food) if __name__ == '__main__': q = Queue() # 生产者 produce = Process(target=producer, args=(q,)) produce.start() # 消费者 custom = Process(target=consumer, args=(q,)) custom.start()
13、JoinableQueue
指的是加入Join阻塞的队列。
与普通队列的区别是,可以等待队列所有数据处理完毕后才执行后面程序
JoinableQueue除了包含Queue的方法外,还有两个特殊的方法。
q.task_done(), 用来发送信号,表示q.get()取出的数据已经被处理完毕。如果调用次数大于队列内项目的数量,将引发ValueError异常。
q.join(), 用来阻塞,只有队列的数据被处理,且每次处理后都调用q.task_done()方法,程序才继续执行。
from multiprocessing import JoinableQueue,Queue q = JoinableQueue() q.put(1) q.put(2) print(q.get()) q.task_done() # 告诉容器已经处理完了一个数据 q.task_done() #有几个就要调用几次 # q.join() # 也是一个阻塞函数 一直到队列中的数据被处理完毕 (task_done的调用次数等于队列中的数据数量) print("处理完成") # print(q.get()) print(q.empty())
from multiprocessing import Process, Queue, JoinableQueue import time import random def producer(q): """生产者生产商品""" for i in range(1,11): time.sleep(random.randint(1,2)) produce = "baozi%s"%i q.put(produce) print('生产者蒸了第%s个包子'%i) def consumer(q): """消费者消费商品""" while True: food = q.get() time.sleep(random.randint(1,2)) print("消费者吃了一个包子:%s"%food) q.task_done() if __name__ == '__main__': q = JoinableQueue() # 生产者 produce = Process(target=producer, args=(q,)) produce.start() # 消费者 custom = Process(target=consumer, args=(q,)) custom.daemon = True custom.start() # 等待生产者生产完包子 produce.join() # 等待所有包子吃完就结束 q.join() print("main over....")

浙公网安备 33010602011771号