1、操作系统的介绍
精简的说的话,操作系统就是一个协调、管理和控制计算机硬件资源和软件资源的控制程序。操作系统位于计算机硬件与应用软件之间,本质也是一个软件。操作系统由操作系统的内核(运行于内核态,管理硬件资源)以及系统调用(运行于用户态,为应用程序员写的应用程序提供系统调用接口)两部分组成,所以,单纯的说操作系统是运行于内核态的,是不准确的。
细说的话,操作系统应该分成两部分功能:
1、隐藏了丑陋的硬件调用接口,为应用程序员提供调用硬件资源的更好,更简单,更清>晰的模型(系统调用接口)。应用程序员有了这些接口后,就不用再考虑操作硬件的细节,专心开发自己的应用程序即可。
2、将应用程序对硬件资源的竞态请求变得有序化。
第三代计算机的操作系统广泛应用了第二代计算机的操作系统没有的关键技术:多道技术
cpu在执行一个任务的过程中,若需要操作硬盘,则发送操作硬盘的指令,指令一旦发出,硬盘上的机械手臂滑动读取数据到内存中,这一段时间,cpu需要等待,时间可能很短,但对于cpu来说已经很长很长,长到可以让cpu做很多其他的任务,如果我们让cpu在这段时间内切换到去做其他的任务,这样cpu不就充分利用了吗。这正是多道技术产生的技术背景
多道技术:
多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。
空间上的复用:将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序。
时间上的复用:当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%,类似于我们小学数学所学的统筹方法。(操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到io时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走cpu的执行权限)
2、并发编程之多进程
一 什么是进程
进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu。
二 进程与程序的区别
程序仅仅只是一堆代码而已,而进程指的是程序的运行过程。同一个程序执行两次,那也是两个进程。
三 并发与并行
无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务
一 并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发
二 并行:同时运行,只有具备多个cpu才能实现并行
单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的)
有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4,
一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术
而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算)
可能被分 配给四个cpu中的任意一个去执行
四 进程的层次结构
无论UNIX还是windows,进程只有一个父进程,不同的是:
- 在UNIX中所有的进程,都是以init进程为根,组成树形结构。父子进程共同组成一个进程组,这样,当从键盘发出一个信号时,该信号被送给当前与键盘相关的进程组中的所有成员。
- 在windows中,没有进程层次的概念,所有的进程都是地位相同的,唯一类似于进程层次的暗示,是在创建进程时,父进程得到一个特别的令牌(称为句柄),该句柄可以用来控制子进程,但是父进程有权把该句柄传给其他子进程,这样就没有层次了。
一个进程的三种状态
开启进程的两种方式
python中的多线程无法利用多核优势,如果想要充分地使用多核CPU的资源(os.cpu\_count\(\)查看),在python中大部分情况需要使用多进程。
Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),该模块与多线程模块threading的编程接口类似。multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,>提供了Process、Queue、Pipe、Lock等组件。
需要再次强调的一点是:与线程不同,进程没有任何共享状态,进程修改的数据,改动仅限于该进程内。
进程:正在进行的一个过程或者说一个任务。而负责执行任务则是cpu
Process类的介绍
创建进程的类
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,可用来开启一个子进程
强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
group参数未使用,值始终为None
target表示调用对象,即子进程要执行的任务
args表示调用对象的位置参数元组,args=(1,2,'egon',)
kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
name为子进程的名称
方法介绍:
p.start():启动进程,并调用该子进程中的p.run()
p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。
如果p还保存了一个锁那么也将不会被释放,进而导致死锁
p.is_alive():如果p仍然运行,返回True
p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间。
属性介绍:
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
Process类的使用
注意:在windows中Process()必须放到# if __name__ == '__main__':下
创建并开启子进程的方式一
##创建子进程方式一
from multiprocessing import Process
import time
def task(name):
print('%s is done'%name)
time.sleep(3)
print('%s is done'%name)
if __name__ == '__main__':
p = Process(target=task,args=('子进程1',)) #实例化得到一个对象
#Process(target=task,kwargs={'name':'子进程1'})
p.start()#仅仅是给操作系统发送个信号 (我父进程有个子进程你去给我开吧);父进程不会等着它开启
#子进程的初始状态跟父进程一样,但运行是独立的
print('主')
#打印
主
子进程1 is done
子进程1 is done
创建并开启子进程的方式二
###方式二
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super().__init__()
self.name = name
def run(self): #固定写法,子进程
print('%s is done'%self.name)
time.sleep(3)
print('%s is done'%self.name)
if __name__ == '__main__':
p = MyProcess('子进程')
p.start() #调用那个run;start会自动调用run方法。
print('主')
#打印:
主
子进程 is done
子进程 is done
查看pid
from multiprocessing import Process
import time,os
def task():
print('%s is running,parent id is <%s>' %(os.getpid(),os.getppid()))#看进程编号,看那个在执行
time.sleep(3)
print('%s is done,parent id is <%s>' %(os.getpid(),os.getppid()))#os.getppid()就是它父的编号
if __name__ == '__main__':
p=Process(target=task,)
p.start()
print('主',os.getpid(),os.getppid())#os.getppid()是pycharm的,主进程的爹
获取进程信息
windows
tasklist
# tasklist |findstr java.exe
方式1,通过进行pid杀死:
taskkill /pid 4612 /f
方式2,通过名称杀死:
taskkill /im QQ.exe /f
linux
ps aux|grep tomcat
kill -s 9 pid
僵尸进程和孤儿进程(了解)
父进程开启一个子进程,他自己结束之后,会等子进程运行完,他才会结束。
所有的子进程都会有个僵尸进程,他死了之后,会保留尸体,父进程可以随时查看他的状态。子进程在父进程一直不死的情况下会有害,多个僵尸进程占用内存。
父进程先死掉,子进程还在就是孤儿进程;在linux系统之上会有一个INIT,这个进程是所有进程它爹,INIT相当于孤儿院,由它来接管;
join方法
在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况
情况一:在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。
情况二:如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用
from multiprocessing import Process
import time, os
def task():
print('%s is done,parent id is %s'%(os.getpid(),os.getppid())) #看进程编号,看哪个在执行
time.sleep(3)
print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))#os.getppid()就是它父的编号
if __name__ == '__main__':
p = Process(target=task, ) #
p.start()
p.join()#主进程在等p结束,这一行不过去永远不会执行下一步,最后执行主进程;卡住的是主进程而绝非子进程p
print('主',os.getpid(),os.getppid())
print(p.pid) #进程结束掉之后,子进程运行完之后可以查看它的pid,但是主进程结束后就没有了,僵尸儿子才会被回收掉。在cmd命令下查看5300是否还存在,结果是被回收了
#打印:
is done,parent id is 4780
is done,parent id is 4780
主 4780 3580
View Code
from multiprocessing import Process
import time, os
def task(name):
print('%s is dones'%name)
time.sleep(3)
if __name__ == '__main__':
p1 = Process(target=task, args=('子进程1',)) #
p2 = Process(target=task, args=('子进程2',))
p3 = Process(target=task, args=('子进程3',))
p1.start()
p2.start()
p3.start()
print('主',os.getpid(),os.getppid())
#打印 结果不唯一,p1.start()、p2、p3只是给给操作系统发个信息,操作系统先运行谁说不准,多打印几次结果就不一样了
主 4804 3580
子进程2 is dones
子进程1 is dones
子进程3 is dones
View Code
from multiprocessing import Process
import time, os
def task(name, n):
print('%s is dones'%name) #看进程编号,看那个在执行
time.sleep(n)
if __name__ == '__main__':
start = time.time()
p1 = Process(target=task, args=('子进程1',5)) #
p2 = Process(target=task, args=('子进程2',3))
p3 = Process(target=task, args=('子进程3',2))
p_l = [p1,p2,p3]
# p1.start()
# p2.start()
# p3.start() #也可以改成for循环
for p in p_l:
p.start() #进程只要start就会开始运行了,p1、p2、p3.start()时,系统已经有3个并发的进程了。
# p1.join() #join是让主进程等,而p1-p3仍然是并发执行的,p1.join()的时候,其余p2、p3仍然在运行,等p1.join()结束,可能p2、p3早已经结束了,这样p2.join()、p3.join()直接通过检测无需等待。
# p2.join()
# p3.join()
for p in p_l:
p.join()
print('主',(time.time()-start)) #等的就是那个最长的时间,程序仍然是并发执行,不是串连执行
打印:
子进程1 is dones
子进程3 is dones
子进程2 is dones
主 5.244300127029419
View Code
from multiprocessing import Process
import time, os
def task(name, n):
print('%s is dones'%name) #看进程编号,看那个在执行
time.sleep(n)
if __name__ == '__main__':
start = time.time()
p1 = Process(target=task, args=('子进程1',5)) #
p2 = Process(target=task, args=('子进程2',3))
p3 = Process(target=task, args=('子进程3',2))
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
print('主',(time.time()-start)) #这样就是串行了,等1结束,然后等2、等3
#打印
子进程1 is dones
子进程2 is dones
子进程3 is dones
主 10.41359567642212
View Code
is_alive()、terminate()、name属性
### is_alive()判断是否存活
from multiprocessing import Process
import time, os
def task():
print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))
time.sleep(3)
print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))
if __name__ == '__main__':
p = Process(target=task, ) #
p.start()
print(p.is_alive())
p.join()
print('主',os.getpid(),os.getppid())
print(p.pid)
print(p.is_alive()) #看看p是否还活着
#打印:
True
is done,parent id is 3068
is done,parent id is 3068
主 3068 3708
False
View Code
###terminate() 、 name属性
from multiprocessing import Process
import time, os
def task():
print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))
time.sleep(3)
print('%s is done,parent id is %s'%(os.getpid(),os.getppid()))
if __name__ == '__main__':
p = Process(target=task, name= 'sub-process') #可以用关键字参数来指定进程名字
p.start()
p.terminate()#杀死这个p进程,它只是给操作系统发个信息,杀死进程是要回收内存空间的是由操作系统说了算
time.sleep(3)#不加这个杀不死,要等操作系统缓存下
print(p.is_alive())
print('主')
print(p.name) #不指定就默认Process-1
#打印:
False
主
sub-process
View Code
练习题
1、进程之间的内存空间是共享的还是隔离的?下述代码的执行结果是什么?
进程内存空间是隔离的。
from multiprocessing import Process
n=100 #在windows系统中应该把全局变量定义在if __name__ == '__main__'之上就可以了
def work():
global n
n=0
print('子进程内: ',n)
if __name__ == '__main__':
p=Process(target=work)
p.start()
p.join()##确保子进程已经运行过了
print('主进程内: ',n)
打印:
子进程内: 0
主进程内: 100
View Code
2、基于多进程实现并发的套接字通信?
##服务端
from socket import *
from multiprocessing import Process
def talk(conn):
while True:
try:
data = conn.recv(1024) #干通信的活
if not data:break
conn.send(data.upper())
except ConnectionRefusedError:
break
conn.close()
def server(ip, port):
server = socket(AF_INET, SOCK_STREAM)
server.bind((ip, port))
server.listen(5)
while True:
conn,addr = server.accept() ##主进程专门干链接的活,一直建链接,每建成一个链接就起一个进程跟客户端通信
p = Process(target=talk,args=(conn,))
p.start()
server.close()
if __name__ == '__main__':
server('127.0.0.1',8080)
View Code
##客户端
from socket import *
client = socket(AF_INET, SOCK_STREAM)
client.connect(('127.0.0.1', 8080))
while True:
msg = input('>>>:').strip()
if not msg :continue
client.send(msg.encode('utf-8'))
data = client.recv(1024)
print(data.decode('utf-8'))
View Code
3、改写下列程序,分别别实现下述打印效果
# from multiprocessing import Process
# import time
# import random
#
# def task(n):
# time.sleep(random.randint(1,3))
# print('-------->%s' %n)
#
# if __name__ == '__main__':
# p1=Process(target=task,args=(1,))
# p2=Process(target=task,args=(2,))
# p3=Process(target=task,args=(3,))
#
# p1.start()
# p2.start()
# p3.start()
#
# p1.join()
# p2.join()
# p3.join()
# print('-------->4')
打印:#---->4最后打印
-------->1
-------->3
-------->2
-------->4
from multiprocessing import Process
import time
import random
def task(n):
time.sleep(random.randint(1,3))
print('-------->%s' %n)
if __name__ == '__main__':
p1=Process(target=task,args=(1,))
p2=Process(target=task,args=(2,))
p3=Process(target=task,args=(3,))
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
print('-------->4')
打印:#按顺序输出
-------->1
-------->2
-------->3
-------->4
View Code
守护进程
主进程创建子进程,然后将该进程设置成守护自己的进程,守护进程就好比崇祯皇帝身边的老太监,崇祯皇帝已死老太监就跟着殉葬了。
守护进程需要强调两点:
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
如果我们有两个任务需要并发执行,那么开一个主进程和一个子进程分别去执行就ok了,如果子进程的任务在主进程任务结束后就没有存在的必要了,那么该子进程应该在开启前就被设置成守护进程。主进程代码运行结束,守护进程随即终止。
from multiprocessing import Process
import time
import random
def task(name):
print('%s is piaoing' %name)
time.sleep(random.randrange(1,3))
print('%s is piao end' %name)
if __name__ == '__main__':
p=Process(target=task,args=('egon',))
p.daemon=True #一定要在p.start()前设置,设置p为守护进程,禁止p创建子进程,并且父进程代码执行结束,p即终止运行
p.start()
print('主') #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了
#打印:
主 #把p.daemon=True注释掉,就可以打印出egon is piaoing egon is piao end
View Code
from multiprocessing import Process
import time
def task(name):
print('%s is done'%name)
time.sleep(3)
p = Process(target=time.sleep, args=(3,)) #再开子进程会报错,守护进程不让开
p.start()
if __name__ == '__main__':
p = Process(target=task, args=('子进程1',))
p.daemon = True #要在开始之前设置
p.start()
p.join() #确保了守护进程确实运行完毕了
print('主') #只要终端打印出这一行内容,那么守护进程p也就跟着结束掉了。
打印:
子进程1 is done
Process Process-1:
Traceback (most recent call last):
File
.....
AssertionError: daemonic processes are not allowed to have children
主
View Code
#练习
from multiprocessing import Process
import time
def foo(): #守护进程
print(123)
time.sleep(1)
print("end123")
def bar():
print(456)
time.sleep(3)
print("end456")
if __name__ == '__main__':
p1=Process(target=foo)
p2=Process(target=bar)
p1.daemon=True
p1.start()
p2.start()
print("main-------") #运行到这一行的时候p1 p2还没出来;
打印:
main------- #只要它出现,p1立马就死了;
end456
View Code
互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱。
如何控制,就是加锁处理。而互斥锁的意思就是互相排斥,如果把多个进程比喻为多个人,互斥锁的工作原理就是多个人都要去争抢同一个资源:卫生间,一个人抢到卫生间后上一把锁,其他人都要等着,等到这个完成任务后释放锁,其他人才有可能有一个抢到......所以互斥锁的原理,就是把并发改成穿行,降低了效率,但保证了数据安全不错乱。
from multiprocessing import Process
import time
def task(name):
print('%s 1' %name)
time.sleep(1)
print('%s 2' %name)
time.sleep(1)
print('%s 3' %name)
if __name__ == '__main__':
for i in range(3):
p=Process(target=task,args=('进程%s' %i,))
p.start()
打印:#竞争 谁抢这终端了谁打印
进程0 1
进程1 1
进程2 1
进程0 2
进程1 2
进程2 2
进程0 3
进程1 3
进程2 3
View Code
from multiprocessing import Process, Lock
import time
def task(name, mutex): #所有的子进程来抢这把锁,然后来运行下面这段代码;运行完之后,其他字进程才进入
mutex.acquire() #加锁
print('%s 1' %name)
time.sleep(1)
print('%s 2' %name)
time.sleep(1)
print('%s 3' %name)
mutex.release() #运行完之后要释放
if __name__ == '__main__':
mutex = Lock() #父进程里边早出来这个变量对象,子进程也会拷贝一份
for i in range(3):
p=Process(target=task,args=('进程%s' %i, mutex))#传给子进程,所有的子进程用一把锁
p.start()
#打印:
进程0 1
进程0 2
进程0 3
进程1 1
进程1 2
进程1 3
进程2 1
进程2 2
进程2 3
View Code
模拟抢票
#查看票数的时候是并发,购票是串,一个一个来,加个互斥锁
#文件db.txt的内容为:{"count":1} #一定要用双引号,不然json无法识别
# -*- coding:utf-8 -*-
from multiprocessing import Process, Lock
import time
import json
def search(name):
time.sleep(1)
data = json.load(open('db.txt', 'r', encoding='utf-8'))
print('%s 正在查票,余票【%s】' % (name,data['count']))
def get(name):
time.sleep(1) # 模拟网络延迟
data = json.load(open('db.txt', 'r', encoding='utf-8'))
print('%s 正在查票,余票【%s】' % (name,data['count']))
if data['count'] > 0:
data['count'] -= 1
time.sleep(3)
json.dump(data, open('db.txt', 'w', encoding='utf-8'))
print('%s抢票成功'% name)
else:
print('余票不足')
def task(name, mutex):
search(name)
mutex.acquire()
get(name)
mutex.release()
if __name__ == '__main__':
mutex = Lock()
for i in range(10): # 模拟并发10个用户
p = Process(target=task, args=('路人%s' % i, mutex))
p.start()
打印:
路人1 正在查票,余票【1】
路人0 正在查票,余票【1】
路人3 正在查票,余票【1】
路人2 正在查票,余票【1】
路人4 正在查票,余票【1】
路人5 正在查票,余票【1】
路人6 正在查票,余票【1】
路人7 正在查票,余票【1】
路人8 正在查票,余票【1】
路人9 正在查票,余票【1】
路人1 正在查票,余票【1】
路人1抢票成功
路人0 正在查票,余票【0】
余票不足
路人3 正在查票,余票【0】
余票不足
路人2 正在查票,余票【0】
余票不足
路人4 正在查票,余票【0】
余票不足
路人5 正在查票,余票【0】
余票不足
路人6 正在查票,余票【0】
余票不足
路人7 正在查票,余票【0】
余票不足
路人8 正在查票,余票【0】
余票不足
路人9 正在查票,余票【0】
余票不足
View Code
互斥锁和join的区别
互斥锁就是把并发变成串行,保证多个进程去修改同一个数据的时候大家一个一个修改,牺牲了效率保证了数据的安全;
join也可以使程序串行
发现使用join将并发改成串行,确实能保证数据安全,但问题是连查票操作也变成只能一个一个人去查了,很明显大家查票时应该是并发地去查询而无需考虑数据准确与否,此时join与互斥锁的区别就显而易见了,join是将一个任务整体串行,而互斥锁的好处则是可以将一个任务中的某一段代码串行,比如只让task函数中的get任务串行。
from multiprocessing import Process,Lock
import json
import time
def search(name):
time.sleep(1)
dic=json.load(open('db.txt','r',encoding='utf-8'))
print('<%s> 查看到剩余票数【%s】' %(name,dic['count']))
def get(name):
time.sleep(1)
dic=json.load(open('db.txt','r',encoding='utf-8'))
if dic['count'] > 0:
dic['count']-=1
time.sleep(3)
json.dump(dic,open('db.txt','w',encoding='utf-8'))
print('<%s> 购票成功' %name)
else:
print('<%s> 购票失败' %name)
def task(name,):
search(name)
# mutex.acquire()
get(name)
# mutex.release()
if __name__ == '__main__':
# mutex=Lock()
for i in range(10):
p=Process(target=task,args=('路人%s' %i,))
p.start()
p.join()#把进程整个代码都串行了,看票也是串行的
打印:
<路人0> 查看到剩余票数【0】
<路人0> 购票失败
<路人1> 查看到剩余票数【0】
<路人1> 购票失败
<路人2> 查看到剩余票数【0】
<路人2> 购票失败
<路人3> 查看到剩余票数【0】
<路人3> 购票失败
<路人4> 查看到剩余票数【0】
<路人4> 购票失败
<路人5> 查看到剩余票数【0】
<路人5> 购票失败
<路人6> 查看到剩余票数【0】
<路人6> 购票失败
<路人7> 查看到剩余票数【0】
<路人7> 购票失败
<路人8> 查看到剩余票数【0】
<路人8> 购票失败
<路人9> 查看到剩余票数【0】
<路人9> 购票失败
View Code
总结
加锁可以保证多个进程修改同一块数据时,同一时间只能有一个任务可以进行修改,即串行地修改,没错,速度是慢了,但牺牲了速度却保证了数据安全。
虽然可以用文件共享数据实现进程间通信,但问题是:
1、效率低(共享数据基于文件,而文件是硬盘上的数据)
2、需要自己加锁处理
因此我们最好找寻一种解决方案能够兼顾:
1、效率高(多个进程共享一块内存的数据)
2、帮我们处理好锁问题。
这就是mutiprocessing模块为我们提供的基于消息的IPC通信机制:队列和管道。
队列和管道都是将数据存放于内存中,而队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,因而队列才是进程间通信的最佳选择。
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列的使用
进程之间内存之间是互相隔离的,但可以共享(加锁)数据,读写效率低;我用一块共享内存,读写效率就高了,也解决了加锁的问题
IPC,进程之间的通信:队列和管道,用的都是内存的空间,共享内存;队列就是管道加锁实现的。
队列,先进先出
创建队列的类(底层就是以管道和锁定的方式实现):
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。
maxsize是队列中允许最大项数,省略则无大小限制。
但需要明确:
1、队列内存放的是消息而非大数据
2、队列占用的是内存空间,因而maxsize即便是无大小限制也受限于内存大小
主要方法:
q.put方法用以插入数据到队列中。
q.get方法可以从队列读取并且删除一个元素。
使用:
from multiprocessing import Queue
q=Queue(3)
q.put('hello')
q.put({'a':1})
q.put([3,3,3,])
print(q.full()) #True
# q.put(4) #只能放三个数据,然后会把它给锁上,卡着不能运行了,阻塞了
print(q.get()) #hello
print(q.get()) #{'a':1}
print(q.get()) #[3,3,3,]
print(q.empty()) #清空了
print(q.get())
打印: #最后空数据,卡着了,取不到了
True
hello
{'a': 1}
[3, 3, 3]
True
生产者消费者模型
生产者指的是生产数据的任务,消费者指的是处理数据的任务,在并发编程中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。
什么是生产者和消费者模式?
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
这个阻塞队列就是用来给生产者和消费者解耦的。
from multiprocessing import Process,Queue
import time
def producer(q):
for i in range(6): #模拟生产6个数据
res='包子%s' %i
time.sleep(0.5)
print('生产者生产了%s' %res)
q.put(res) #生产完不是直接给消费者,丢到队列里边
def consumer(q):
while True: #不知道接几次
res=q.get() #接结果
if res is None:break #消费者最后会收到那个None,这样就不会卡那了
time.sleep(1)
print('消费者吃了%s' % res)
if __name__ == '__main__':
#容器
q=Queue()
#生产者们,生产者可能有多个
p1=Process(target=producer,args=(q,)) #传参
p2=Process(target=producer,args=(q,))
p3=Process(target=producer,args=(q,))
#消费者们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
p1.join() #主进程保证p1这个子进程运行完了
p2.join()
p3.join()
q.put(None) #None代表结束信号,有几个消费者来几个信号;;应该是在主进程里边确保所有的生产者都生产结束之后才发结束信号
q.put(None) #有几个消费者就应该要有几个信号,
print('主')
打印:
生产者生产了包子0
生产者生产了包子0
生产者生产了包子0
生产者生产了包子1
生产者生产了包子1
生产者生产了包子1
消费者吃了包子0
生产者生产了包子2
生产者生产了包子2
消费者吃了包子0
生产者生产了包子2
生产者生产了包子3
生产者生产了包子3
生产者生产了包子3
消费者吃了包子0
生产者生产了包子4
消费者吃了包子1
生产者生产了包子4
生产者生产了包子4
生产者生产了包子5
生产者生产了包子5
生产者生产了包子5
主
消费者吃了包子1
消费者吃了包子1
消费者吃了包子2
消费者吃了包子2
消费者吃了包子2
消费者吃了包子3
消费者吃了包子3
消费者吃了包子3
消费者吃了包子4
消费者吃了包子4
消费者吃了包子4
消费者吃了包子5
消费者吃了包子5
消费者吃了包子5
View Code
用处:程序中有两类角色,一类是生产数据、另一类是处理数据,就可以引入生产者消费者模型来解决耦合的问题; 生产者<--->队列<--->消费者
好处:平衡生产者与消费者之间的速度差;程序解开耦合。
用process下每个实现,生产者、消费者、和那个q都要在一个计算机上,集中式的;带来的问题:稳定性(计算机断了,集中式都断掉了)、效率和性能(一台计算机总归是有极限的)。 把程序各个组件分配出去,分给各个机器,效率和稳定性都提升了。基于网络套接字,把消息发来取走;基于网络通信的消息队列典型代表是Rabbitmq。
JoinableQueue
在有多个生产者和多个消费者时,有几个消费者就需要发送几次结束信号:相当low,可以用joinableQueue来解决
JoinableQueue的实例p除了与Queue对象相同的方法之外还具有:
q.task_done():使用者使用此方法发出信号,表示q.get()的返回项目已经被处理。如果调用此方法的次数大于从队列中删除项目的数量,将引发ValueError异常
q.join():生产者调用此方法进行阻塞,直到队列中所有的项目均被处理。阻塞将持续到队列中的每个项目均调用q.task_done()方法为止
from multiprocessing import Process,JoinableQueue
import time
def producer(q):
for i in range(2):
res='包子%s' %i
time.sleep(0.5)
print('生产者生产了%s' %res)
q.put(res)
q.join() #所有的生产者生产完了之后,队列都取没了就算结束了; 生产完了之后就在这等着,数据都取走之后就不等了
def consumer(q):
while True:
res=q.get() #卡在这里;东西取没了就算结束
if res is None:break
time.sleep(1)
print('消费者吃了%s' % res)
q.task_done() #提供的接口,从队列里边取走的信号 消费者给生产者发个信号,告诉生产者有一个数据取走了;跟原来反着的,原来是生产者给消费者发信号
if __name__ == '__main__':
#容器
q=JoinableQueue()
#q.join()#等队列执行完,队列取没了就算完了
#生产者们
p1=Process(target=producer,args=(q,))
p2=Process(target=producer,args=(q,))
p3=Process(target=producer,args=(q,))
#消费者们
c1=Process(target=consumer,args=(q,))
c2=Process(target=consumer,args=(q,))
c1.daemon=True #把c1、c2设置成守护进程,这样就可以正常结束了
c2.daemon=True
p1.start()
p2.start()
p3.start()
c1.start()
c2.start()
p1.join() #保证生产者结束完了
p2.join()
p3.join() #执行到这一步,可以保证 q被取空了,消费者已经都完全取走了。
print('主') #这一行运行完之后,消费者就没意义存在了,这样就用到了守护进程
打印
生产者生产了包子0
生产者生产了包子0
生产者生产了包子0
生产者生产了包子1
生产者生产了包子1
生产者生产了包子1
消费者吃了包子0
消费者吃了包子0
消费者吃了包子0
消费者吃了包子1
消费者吃了包子1
消费者吃了包子1
主
View Code