进程

[多进程](https://www.cnblogs.com/linhaifeng/articles/7428874.html)

**CPU多道技术**
1.空间上的复用
多个程序共用一套计算机硬件

2.时间上的复用
切换+保存状态
1.当一个程序遇到IO操作 操作系统会剥夺该程序的cpu执行权限(提高了cpu的利用率 并且也不影响程序的执行效率)
2.当一个程序长时间占用cpu 操作系统也会剥夺该程序的cpu执行权限(降低了程序的执行效率)

# 进程线程的特点
1. 每个进程都有自己 独立的 内存地址
2. 线程之间的切换 相对于 进程之间切换 更为方便,代价也更低。
3. 对于IO密集型的任务,使用多线程还是能提高一下CPU使用率。
4. 对于CPU密集型的任务,Python中的多线程其实是个鸡肋……没卵用……在Python的解释器CPython中存在一个互斥锁。简单来讲就是同一时间只能有一个线程在执行,其它线程都处于block模式。
5. 要想在py中充分利用多核cpu,就只能用多进程了。虽然代价高了些,但是比起并行计算带来的性能提升这些也微不足道了。

* 进程与线程的关系区别
一个程序至少有一个进程,一个进程至少有一个线程.(进程可以理解成线程的容器)进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈)但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源. 一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
进程:本质上就是一段程序的运行过程
线程:最小的执行单元(实例)
进程:最小的资源单位
程序计数器: 1.寄存器:cpu与内存中间的一级二级三级缓存,缓存存的是进程线程之间的切换时要保存状态的变量等关键数据、2.栈
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行

## 进程
**跑起来的程序才叫进程**
多个进程之间 数据隔离 内存不共享

进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。我们编写的程序用来描述进程要完成哪些功能以及如何完成;数据集则是程序在执行过程中所需要使用的资源;进程控制块用来记录进程的外部特征,描述进程的执行变化过程,系统可以利用它来控制和管理进程,它是系统感知进程存在的唯一标志。
9f16ea7d1036dbae18468625bea977b5.png  

6fbc0099d61f78ffe79bb7b0dd8bc44b.png  

**开启多进程的第一个方法:process类实例化**
f01cce3cdc6c07b553ae360295d236aa.png  

```
windows创建进程会将代码以模块的方式 从上往下执行一遍
linux会直接将代码完完整整的拷贝一份

windows创建进程一定要在if __name__ == '__main__':代码块内创建 否则报错
```
```python
from multiprocessing import Process
import time
def f(name):
time.sleep(1)
print('hello', name,time.ctime())

if __name__ == '__main__':
p_list=[]
for i in range(3):
p = Process(target=f, args=('alvin',))
p_list.append(p)
p.start()
for i in p_list:
p.join()
print('end')
```

**开启多进程的第二个方法:继承process类**
需要重写run方法
48b21b7b2ba80f40f7b1140c17f69680.png  
继承实现多进程
```python
from multiprocessing import Process
import time

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

def run(self): #重写run方法
time.sleep(1)
print ('hello', self.name,time.ctime())

if __name__ == '__main__':
p_list=[]
for i in range(3):
p = MyProcess()
p.daemon=True #守护进程,跟守护线程用法不一样
p.start()
p_list.append(p)

for p in p_list:
p.join()
print('end')
```
**需求:所有子进程都运行完,才运行某行代码**
6d5c436e33bffe3153f380c02359ede0.png  
显示进程ID
```python
from multiprocessing import Process
import os
import time
def info(title):

print("title:",title)
print('parent process:', os.getppid()) #父进程的PID
print('process id:', os.getpid()) #该进程 的PID

def f(name):
info('function f')
print('hello', name)

if __name__ == '__main__':
info('main process line')
time.sleep(1)
print("------------------")
p = Process(target=info, args=('yuan',))
p.start()
p.join()
```
注意:
显示的第一个父进程是Pycharm
在主进程运行过程中如果想并发地执行其他的任务,我们可以开启子进程,此时主进程的任务与子进程的任务分两种情况
情况一:在主进程的任务与子进程的任务彼此独立的情况下,主进程的任务先执行完毕后,主进程还需要等待子进程执行完毕,然后统一回收资源。
情况二:如果主进程的任务在执行到某一个阶段时,需要等待子进程执行完毕后才能继续执行,就需要有一种机制能够让主进程检测子进程是否运行完毕,在子进程执行完毕后才继续执行,否则一直在原地阻塞,这就是join方法的作用

p.join()是让主线程等待p的结束,卡住的是主进程而绝非子进程p

**在处理数据时,必须加锁**
fac837ccb9a4da6a587acf0e164445d7.png  
守护进程:当一个进程对象设置为守护进程,那么主程序只要执行结束,该进程立即结束
```
os.getpid() #查看当前程序PID
os.getppid() #查看当前进程的父进程PID
进程对象.start() #向OS申请创建一个子进程
进程对象.join() #主进程代码等待子进程运行结束 才继续运行
进程对象.is_alive() #查看进程对象是否存活
进程对象.terminate() # 向OS申请杀死进程对象(杀死子进程p)
进程对象.daemon = True # 将该进程设置为守护进程 必须放在start语句之前 否则报错
```

**互斥锁(同步锁)**
当多个进程操作同一份数据的时候 会造成数据的错乱
这个时候必须加锁处理
将并发变成串行
虽然降低了效率但是提高了数据的安全
注意:
1.锁不要轻易使用 容易造成死锁现象
2.只在处理数据的部分加锁 不要在全局加锁

锁必须在主进程中产生 交给子进程去使用
**互斥锁应用--抢票软件**
```python
from multiprocessing import Process,Lock
import time
import json

# 查票
def search(i):
with open('data','r',encoding='utf-8') as f:
data = f.read()
t_d = json.loads(data)
print('用户%s查询余票为:%s'%(i,t_d.get('ticket')))
# 买票
def buy(i):
with open('data','r',encoding='utf-8') as f:
data = f.read()
t_d = json.loads(data)
time.sleep(1)
if t_d.get('ticket') > 0:
# 票数减一
t_d['ticket'] -= 1
# 更新票数
with open('data','w',encoding='utf-8') as f:
json.dump(t_d,f)
print('用户%s抢票成功'%i)
else:
print('没票了')

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()
```

**进程类构造方法**
```python
构造方法:
Process([group [, target [, name [, args [, kwargs]]]]])
  group: 线程组,目前还没有实现,库引用中提示必须是None;
  target: 要执行的方法;
  name: 进程名;
  args/kwargs: 要传入方法的参数。

实例方法:
  is_alive():返回进程是否在运行。
  join([timeout]):阻塞当前上下文环境的进程程,直到调用此方法的进程终止或到达指定的timeout(可选参数)。
  start():进程准备就绪,等待CPU调度
  run():strat()调用run方法,如果实例进程时未制定传入target,这star执行t默认run()方法。
  terminate():不管任务是否完成,立即停止工作进程

属性:
  daemon:和线程的setDeamon功能一样
  name:进程名字。
  pid:进程号。
```
例子
```python
import time
from multiprocessing import Process

class MyProcess(Process):
def __init__(self,num):
super(MyProcess,self).__init__()
self.num=num
def run(self):
time.sleep(1)
print(self.pid)
print(self.is_alive())
print(self.num)
time.sleep(1)

if __name__ == '__main__':
p_list=[]
for i in range(10):
p = MyProcess(i)
#p.daemon=True
p_list.append(p)

for p in p_list:
p.start()
# for p in p_list:
# p.join()

print('main process end')
```

# 并发并行、同步异步、阻塞非阻塞

* 并发 & 并行

并发:指系统具有处理多个任务(动作)的能力
并行:指系统具有 同一时刻 处理多个任务(动作)的能力,是 并发 的一种

* 同步&异步

同步:当进程执行到一个IO操作时候(等待外部数据)--等待不能干别的
异步:当进程执行到一个IO操作时候(等待外部数据)--不等待可干别的:可干别的直到数据接受成功,再回来处理

* 阻塞非阻塞

阻塞: 阻塞调用是指调用结果返回之前,当前线程会被挂起(线程进入非可执行状态,在这个状态下,cpu不会给线程分配时间片,即线程暂停运行)。函数只有在得到结果之后才会返回。
非阻塞:非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
4dae5c08de62bfb7540174d005feccda.png  


# 进程间通讯
### 队列实现通信
队列:先进先出(堆结构)
```python
"""
堆结构:队列--先进先出
栈结构: 后进先出 例如queue.LifoQueue
"""
from multiprocessing import Queue

q = Queue(5) # 括号内可以传参数 表示的是这个队列的最大存储数
# 往队列中添加数据
q.put(1)
q.put(2)
# print(q.full()) # 判断队列是否满了(多进程 不支持full)
q.put(3)
q.put(4)
q.put(5)
# print(q.full())
# q.put(6) # 当队列满了之后 再放入数据 不会报错 会原地等待 直到队列中有数据被取走(阻塞态)

print(q.get())
print(q.get())
print(q.get())
print(q.empty()) # 判断队列中的数据是否取完 (多进程 不支持empty)
print(q.get())
print(q.get())
print(q.empty())
# print(q.get_nowait()) # 取值 没有值不等待直接报错(多进程 不支持get_nowait)
# print(q.get()) # 当队列中的数据被取完之后 再次获取 程序会阻塞 直到有人往队列中放入值
"""
注意:多进程 不支持full,get_nowait,empty用法
"""
```
1、使用Queue+进程 实现的生产者消费者模型代码

```python
import time
import random
from multiprocessing import Process,Queue

def producer(q,name,food):
for i in range(2):
time.sleep(random.random())
fd = '%s%s'%(food,i)
q.put(fd)
print('%s生产了一个%s'%(name,food))

def consumer(q,name):
while True:
food = q.get()
if not food:break
time.sleep(random.randint(1,3))
print('%s吃了%s'%(name,food))

def cp(c_count,p_count):
q = Queue(10)
for i in range(c_count):
Process(target=consumer, args=(q, '灰太狼')).start()
p_l = []
for i in range(p_count):
p1 = Process(target=producer, args=(q, '喜洋洋', '包子'))
p1.start()
p_l.append(p1)
for p in p_l:p.join()
for i in range(c_count):
q.put(None)
if __name__ == '__main__':
cp(2,3)
----------------结果:
喜洋洋生产了一个包子
喜洋洋生产了一个包子
喜洋洋生产了一个包子
喜洋洋生产了一个包子
喜洋洋生产了一个包子
喜洋洋生产了一个包子
灰太狼吃了包子1
灰太狼吃了包子0
灰太狼吃了包子0
灰太狼吃了包子0
灰太狼吃了包子1
灰太狼吃了包子1
```

2、使用joinablequeue实现队列
(1)消费者不需要判断从队列里拿到None再退出执行消费者函数了
(2)消费者每次从队列里面q.get()一个数据,就使用q.task_done(),告诉队列你已经从队列中取出了一个数据 并且处理完毕了
(3)生产者for循环生产完所有产品,需要q.join()阻塞一下,对这个队列进行阻塞。
(4)启动一个生产者,启动一个消费者,并且这个消费者做成守护进程,然后生产者需要p.join()阻塞一下。
(5)我启动了生产者之后,生产者函数一直在生成数据,直到生产完所有数据将队列q.join()一下,意思是当我生产的数据都被消费者消费完之后 队列的阻塞才结束。
(6)结束过程:消费者这边是每消费完一个数据,就使用q.task_done(),告诉队列你已经从队列中取出了一个数据 并且处理完毕了,直到所有的数据都被消费完之后,生产者函数这边的队列.阻塞结束了,队列阻塞结束了生产者函数执行结束了。生产者函数结束了,那么p.join()生产者进程对象就结束了。生产者进程对象结束了整个主进程的代码就执行结束了。主进程代码结束了守护进程及消费者进程也结束了

```python
import time
import random
from multiprocessing import JoinableQueue,Process
def producer(q,name,food):
for i in range(5):
time.sleep(random.random())
fd = '%s%s'%(food,i+1)
q.put(fd)
print('%s生产了一个%s'%(name,food))
q.join()#(3)生产者for循环生产完所有产品,需要q.join()阻塞一下,对这个队列进行阻塞。
#(5)我启动了生产者之后,生产者函数一直在生成数据,直到生产完所有数据将队列q.join()一下,意思是当我生产的数据都被消费者消费完之后 队列的阻塞才结束。
def consumer(q,name): #(1)消费者不需要像Queue那样判断从队列里拿到None再退出执行消费者函数了
while True:
food = q.get()
time.sleep(random.random())
print('%s吃了%s'%(name,food))
q.task_done() #(2)使用q.task_done(),告诉队列你已经从队列中取出了一个数据 并且处理完毕了
if __name__ == '__main__':
jq = JoinableQueue()
p =Process(target=producer,args=(jq,'喜洋洋','包子')) #
p.start() #(4)启动一个生产者,启动一个消费者,并且这个消费者做成守护进程,然后生产者需要p.join()阻塞一下。
c = Process(target=consumer,args=(jq,'灰太狼'))
c.daemon = True #
c.start()
p.join()
jq.join() #等到队列中数据全部取出
#(6)结束过程:消费者这边是每消费完一个数据给队列返回一个q.task_done(),直到所有的数据都被消费完之后,生产者函数这边的队列.阻塞结束了,队列阻塞结束了生产者函数执行结束了。生产者函数结束了,那么p.join()生产者进程对象就结束了。生产者进程对象结束了整个主进程的代码就执行结束了。主进程代码结束了守护进程即消费者进程也结束了
---------------结果:
喜洋洋生产了一个包子
灰太狼吃了包子1
喜洋洋生产了一个包子
喜洋洋生产了一个包子
喜洋洋生产了一个包子
喜洋洋生产了一个包子
灰太狼吃了包子2
灰太狼吃了包子3
灰太狼吃了包子4
灰太狼吃了包子5

import time
import random
from multiprocessing import JoinableQueue,Process
def producer(q,name,food):
for i in range(5):
time.sleep(random.random())
fd = '%s%s'%(food,i+1)
q.put(fd)
print('%s生产了一个%s'%(name,food))
q.join()
def consumer(q,name):
while True:
food = q.get()
time.sleep(random.random())
print('%s吃了%s'%(name,food))
q.task_done()
if __name__ == '__main__':
jq = JoinableQueue()
p =Process(target=producer,args=(jq,'喜洋洋','包子'))
p.start()
c = Process(target=consumer,args=(jq,'灰太狼'))
c.daemon = True
c.start()
p.join()
```

3、二者区别
1)Queue有多少消费者,就要put多少个None。要在消费者函数添加if 不是真(非None数据)就退出死循环
2)二者效果一样但是从程序员角度看,joinablequeue更加严谨,更符合编程思维

 

 

### 管道实现通信
1. 进程对列multiprocess.Queue 不建议 开销大,用管道
不同的进程之间的队列是 复制+映射关系
2. 管道
导入Pipe
parent_conn, child_conn = Pipe()
父进程与子进程之间建立管道,在父进程中创建管道,子进程实例化时,把child_conn传给args元组
```python
from multiprocessing import Process, Pipe
def f(conn):
conn.send([12, {"name":"yuan"}, 'hello'])
response=conn.recv()
print("response",response)
conn.close()
print("q_ID2:",id(conn))

if __name__ == '__main__':

parent_conn, child_conn = Pipe() #双向管道

print("q_ID1:",id(child_conn))
p = Process(target=f, args=(child_conn,))
p.start()

print(parent_conn.recv()) # prints "[42, None, 'hello']" 收不到的话会阻塞
parent_conn.send("儿子你好!")
p.join()

```
3. Manager
实现多个进程之间数据共享
可共享的type:list,dict, Namespace, Lock, Rlock, Semaphore, BoundedSemaphore, Condition, Event, Barrier, Queue, Value, Array
```python
from multiprocessing import Process, Manager

def f(d, l,n):

d[n] = '1' #{0:"1"}
d['2'] = 2 #{0:"1","2":2}
l.append(n) #[0,1,2,3,4, 0,1,2,3,4,5,6,7,8,9]
#print(l)

if __name__ == '__main__':

with Manager() as manager:
d = manager.dict()#{}
l = manager.list(range(5))#[0,1,2,3,4]

p_list = []

for i in range(10):
p = Process(target=f, args=(d,l,i))
p.start()
p_list.append(p)

for res in p_list:
res.join()

print(d)
print(l)
```

* 进程池
```python
from multiprocessing import Process,Pool
import time,os

def Foo(i):
time.sleep(1)
print(i)
print("son",os.getpid())
return "HELLO %s"%i

def Bar(arg):
print(arg)
# print("hello")
# print("Bar:",os.getpid())

if __name__ == '__main__':

pool = Pool(5)
print("main pid",os.getpid())
for i in range(100):
#pool.apply(func=Foo, args=(i,)) #同步接口 一个一个的跑
pool.apply_async(func=Foo, args=(i,)) #并发

#回调函数: 就是某个动作或者函数执行成功后再去执行的函数
#此处为每个进程完成即执行Bar
pool.apply_async(func=Foo, args=(i,),callback=Bar)# 属于主进程
#意 义:子进程中能放在主进程执行的任务,尽量放在主进程

pool.close()
pool.join() # join与close调用顺序是固定的
print('end')
```
```python
进程池中,close要在join之前
pool.close()
pool.join()
如果不要close():报错
如果不要join() :直接end,其余不执行
如果反过来:报错
```

### 补充

**僵尸进程**
有害
父进程回收子进程资源的两种方式
1.join方法,将回收子进程pid及其他资源
2.父进程正常死亡,将回收子进程pid及其他资源
若子进程执行完毕,父进程既没有执行join()方法,又没有正常执行完毕,那么子进程的pid信息将一直存在,,变成僵尸进程,直到父进程正常结束,再去回收
所有的进程都会步入僵尸进程

**孤儿进程**
无害
子进程没死 父进程意外死亡
针对linux会有儿童福利院(init) 如果父进程意外死亡他所创建的子进程都会被福利院收养
windows也有类似机制

posted @ 2019-08-14 19:04  坚持fighting  阅读(130)  评论(0)    收藏  举报