并发编程之进程

进程介绍:

1. 什么是进程
进程指的是一个程序的运行过程,或者说一个正在执行的程序
所以说进程一种虚拟的概念,该虚拟概念起源操作系统
2. 操作系统发展史
多道技术:
1. 空间上的复用: 多个任务复用/共享内存空间
ps:进程之间的内存空间是互相隔离的
2. 时间上的复用: 多个任务复用/共享cpu的时间
cpu在多个任务之间来回切换
1. 一个任务占用cpu时间过长会被操作系统强行剥夺走cpu的执行权限: 只是为了保证一个并发的效果,反而会降低效率
2. 一个任务遇到io操作了会被操作系统强行剥夺走cpu的执行权限: 为了实现并发的效果,这种情况下的并发可以提升效率

大前提:一个cpu同一时刻只能执行一个任务
串行: 一个任务完完整整运行完毕才能执行下一个任务
并发: 多个任务看起来是同时运行的,单核下就能实现并发(并发=切换+保存状态)
并行: 多个任务是真正意义上的同时运行,只有多核才能实行并行


开启进程的两种方式:
开启子进程的方式一:

from multiprocessing import Process
import time

def task(name):
print('%s is running' %name)
time.sleep(3)
print('%s is done' %name)

在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中
if __name__ == '__main__':
p=Process(target=task,args=('egon',)) #Process(target=task,kwargs={'name':'egon'})
p.start() # 只是向操作系统发送了一个开启子进程的信号
print('主')


开启子进程的方式二:
from multiprocessing import Process
import time

class Myprocess(Process):
def __init__(self,name):
super().__init__()
self.name=name

def run(self):
print('%s is running' %self.name)
time.sleep(3)
print('%s is done' %self.name)

在windows系统上,开启子进程的操作必须放到if __name__ == '__main__'的子代码中
if __name__ == '__main__':
p=Myprocess('egon')
p.start() # 只是向操作系统发送了一个开启子进程的信号
print('主')



join方法:
#join:让主进程在原地等待,等待子进程运行完毕,不会影响子进程的执行
from multiprocessing import Process
import time

def task(name,n):
print('%s is running' %name)
time.sleep(n)
print('%s is done' %name)

if __name__ == '__main__':
# p1=Process(target=task,args=('子1',1))
# p2=Process(target=task,args=('子2',2))
# p3=Process(target=task,args=('子3',3))
#
# start=time.time()
# p1.start()
# p2.start()
# p3.start()
# # time.sleep(5)
#
# p3.join() #3
# p1.join()
# p2.join()
# print('主',(time.time()-start))

start=time.time()
p_l=[]
for i in range(1,4):
p = Process(target=task, args=('子%s' %i, i))
p_l.append(p)
p.start()

for p in p_l:
p.join()
print('主', (time.time() - start))



进程之间内存空间相互隔离:
from multiprocessing import Process

n=100
def task():
global n
n=0

if __name__ == '__main__':
p=Process(target=task)
p.start()
p.join()
print(n)




进程对象其他相关属性和方法:
# 1. 进程pid:每一个进程在操作系统内都有一个唯一的id号,称之为pid
from multiprocessing import Process,current_process
import time

def task():
print('%s is running' %current_process().pid)
time.sleep(30)
print('%s is done' %current_process().pid)

if __name__ == '__main__':
p=Process(target=task)
p.start()
print('主',current_process().pid)



from multiprocessing import Process,current_process
import time,os

def task():
print('%s is running 爹是:%s' %(os.getpid(),os.getppid()))
time.sleep(30)
print('%s is done 爹是:%s' %(os.getpid(),os.getppid()))


if __name__ == '__main__':
p=Process(target=task)
p.start()
print('主:%s 主他爹:%s' %(os.getpid(),os.getppid()))




#2. 进程对象其他相关的属性或方法
from multiprocessing import Process,current_process
import time,os

def task():
print('%s is running 爹是:%s' %(os.getpid(),os.getppid()))
time.sleep(30)
print('%s is done 爹是:%s' %(os.getpid(),os.getppid()))


if __name__ == '__main__':
p=Process(target=task,name='子进程1')
p.start()
# print(p.name) # 子进程的名字
p.terminate() # 把子进程'杀死'
# time.sleep(0.1)
print(p.is_alive()) # 判断子进程是否存活
print('主:%s 主他爹:%s' %(os.getpid(),os.getppid()))


守护进程:

主进程创建守护进程

  其一:守护进程会在主进程代码执行结束后就终止

  其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

注意:进程之间是互相独立的,主进程代码运行结束,守护进程随即终止

#守护进程: 本质就是一个"子进程",该"子进程"的生命周期<=被守护进程的生命周期
from multiprocessing import Process
import time

def task(name):
print('老太监%s活着....' %name)
time.sleep(3)
print('老太监%s正常死亡....' %name)

if __name__ == '__main__':
p=Process(target=task,args=('刘清政',))
p.daemon=True
p.start()
time.sleep(1)
print('皇上:EGON正在死...')


互斥锁:
import json
import time,random
from multiprocessing import Process,Lock

def search(name):
with open('db.json','rt',encoding='utf-8') as f:
dic=json.load(f)
time.sleep(1)
print('%s 查看到余票为 %s' %(name,dic['count']))

def get(name):
with open('db.json','rt',encoding='utf-8') as f:
dic=json.load(f)
if dic['count'] > 0:
dic['count'] -= 1
time.sleep(random.randint(1,3))
with open('db.json','wt',encoding='utf-8') as f:
json.dump(dic,f)
print('%s 购票成功' %name)
else:
print('%s 查看到没有票了' %name)

def task(name,mutex):
search(name) #并发
mutex.acquire()
get(name) #串行
mutex.release()

# with mutex:
# get(name)

if __name__ == '__main__':
mutex = Lock()
for i in range(10):
p=Process(target=task,args=('路人%s' %i,mutex))
p.start()
# p.join() # join只能将进程的任务整体变成串行





僵尸进程与孤儿进程:

一:僵尸进程(有害)
  僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下

我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。

因此,UNⅨ提供了一种机制可以保证父进程可以在任意时刻获取子进程结束时的状态信息:
1、在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。但是仍然为其保留一定的信息(包括进程号the process ID,退出状态the termination status of the process,运行时间the amount of CPU time taken by the process等)
2、直到父进程通过wait / waitpid来取时才释放. 但这样就导致了问题,如果进程不调用wait / waitpid的话,那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即为僵尸进程的危害,应当避免。

  任何一个子进程(init除外)在exit()之后,并非马上就消失掉,而是留下一个称为僵尸进程(Zombie)的数据结构,等待父进程处理。这是每个子进程在结束时都要经过的阶段。如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。  如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。

二:孤儿进程(无害)

  孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。

  孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤 儿进程的父进程设置为init,而init进程会循环地wait()它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init进程就会代表党和政府出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。

我们来测试一下(创建完子进程后,主进程所在的这个脚本就退出了,当父进程先于子进程结束时,子进程会被init收养,成为孤儿进程,而非僵尸进程),文件内容

import os
import sys
import time

pid = os.getpid()
ppid = os.getppid()
print 'im father', 'pid', pid, 'ppid', ppid
pid = os.fork()
#执行pid=os.fork()则会生成一个子进程
#返回值pid有两种值:
#    如果返回的pid值为0,表示在子进程当中
#    如果返回的pid值>0,表示在父进程当中
if pid > 0:
    print 'father died..'
    sys.exit(0)

# 保证主线程退出完毕
time.sleep(1)
print 'im child', os.getpid(), os.getppid()

执行文件,输出结果:
im father pid 32515 ppid 32015
father died..
im child 32516 1

看,子进程已经被pid为1的init进程接收了,所以僵尸进程在这种情况下是不存在的,存在只有孤儿进程而已,孤儿进程声明周期结束自然会被init来销毁。



生产者消费者模型:
'''
1. 什么是生产者消费者模型
生产者:代指生产数据的任务
消费者:代指处理数据的任务
该模型的工作方式:
生产生产数据传递消费者处理

实现方式:
生产者---->队列<------消费者

2. 为何要用
当程序中出现明细的两类任务,一类负责生产数据,一类负责处理数据
就可以引入生产者消费者模型来实现生产者与消费者的解耦合,平衡生产能力与消费能力,从提升效率

3. 如何用
'''

'''
import time,random
from multiprocessing import Process,Queue

def producer(name,food,q):
for i in range(3):
res='%s%s' %(food,i)
time.sleep(random.randint(1,3)) #模拟生产数据的时间
q.put(res)
print('厨师[%s]生产了<%s>' %(name,res))

def consumer(name,q):
while True:
res=q.get()
if res is None:break
time.sleep(random.randint(1,3)) #模拟处理数据的时间
print('吃货[%s]吃了<%s>' %(name,res))

if __name__ == '__main__':
q=Queue()
# 生产者们
p1=Process(target=producer,args=('小Egon','泔水',q))
p2=Process(target=producer,args=('中Egon','屎包子',q))
p3=Process(target=producer,args=('大Egon','腰子汤',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()
p2.join()
p3.join()
q.put(None)
q.put(None)
print('主')
'''


import time,random
from multiprocessing import Process,JoinableQueue

def producer(name,food,q):
for i in range(3):
res='%s%s' %(food,i)
time.sleep(random.randint(1,3)) #模拟生产数据的时间
q.put(res)
print('厨师[%s]生产了<%s>' %(name,res))

def consumer(name,q):
while True:
res=q.get()
time.sleep(random.randint(1,3)) #模拟处理数据的时间
print('吃货[%s]吃了<%s>' %(name,res))
q.task_done()

if __name__ == '__main__':
q=JoinableQueue()
# 生产者们
p1=Process(target=producer,args=('小Egon','泔水',q))
p2=Process(target=producer,args=('中Egon','屎包子',q))
p3=Process(target=producer,args=('大Egon','腰子汤',q))
# 消费者们
c1=Process(target=consumer,args=('刘清正',q))
c2=Process(target=consumer,args=('吴三江',q))
c1.daemon=True
c2.daemon=True

p1.start()
p2.start()
p3.start()
c1.start()
c2.start()

p1.join()
p2.join()
p3.join()
q.join() # 主进程等q结束,即q内数据被取干净了
print('主')





原文地址:https://www.cnblogs.com/zhangpang/p/9594952.html