进程

阅读目录

理论知识

什么是进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
 
总结概念:
  • 在进行中的程序
  • 在计算中资源分配的最小单位
  • 进程与进程之间的数据是隔离的,执行过程是异步的

为什么会出现进程的概念?

  • 合理利用CPU,提高用户体验

注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。

 

进程调度

要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
先来先服务调度算法
短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
短作业优先调度算法
时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。
      显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。
在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。
      在轮转法中,加入到就绪队列的进程有3种情况:
      一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
      另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
      第三种情况就是新创建进程进入就绪队列。
      如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。  
时间片轮转法
前面介绍的各种用作进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程,而且如果并未指明进程的长度,则短进程优先和基于进程长度的抢占式调度算法都将无法使用。
而多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。在采用多级反馈队列调度算法的系统中,调度算法的实施过程如下所述。
(1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。
(2) 当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

(3) 仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。
多级反馈队列调度算法

进程的并行与并发

并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )

  • 资源有限的情况下,两者交替轮流使用资源
  • 从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个任务

并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。

  • 并行是指两者同时执行
  • 从微观上,也就是一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器

区别:

并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。

进程的三状态图

  • 就绪:进程创建完毕后,会进入就绪状态
  • 运行:等待时间片,使用CPU,完毕后返回时间片到就绪状态
  • 阻塞:运行时,遇到io操作,打开文件/读写文件,到阻塞,阻塞完毕后再到就绪状态

同步异步阻塞非阻塞

同步:

  • 有几件事情,先做一件,做完一件在做一件
  • 程序顺序执行,多个任务之间串行执行

异步:

  • 有几件事情,同时完成

阻塞

  • input /accep t/recv /sleep /recvfrom
  • 程序由于不符合某个条件或者要等待某个条件满足在某一个地方进入等待状态

非阻塞

  • 所有不阻塞的程序
  • 设为非阻塞 :sk.setblocking(false)

 

import socket
sk = socket.socket()
sk.setblocking(False)
sk.bind(('127.0.0.1',9000))
sk.listen()
while True:
    try:
        conn,addr = sk.accept()
        break
    except BlockingIOError:
        print('没人连我')
conn.recv

进程ID查询

import os,time
print(os.getpid(),os.getppid())
# getpid process id   获取当前进程号
# getppid parent process id 获取当前父进程号
time.sleep(1)
print(os.getpid())

# 子进程和父进程之间的关系
# pycharm启动了py文件
# py文件就是子进程
# pycharm就是父进程

执行结果:

18816  8352
18816

进程的创建与结束

进程的创建

  但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。

  而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程:

  1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)

  2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)

  3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)

  4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)

  无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。  

进程的结束

  1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)

  2. 出错退出(自愿,python a.py中a.py不存在)

  3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)

  4. 被其他进程杀死(非自愿,如kill -9)

在python程序中的进程操作

multiprocess模块

仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 之所以叫multi是取自multiple的多功能的意思,在这个包中几乎包含了和进程有关的所有子模块。由于提供的子模块非常多,为了方便大家归类记忆,我将这部分大致分为四个部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。

multiprocess.process模块

process模块介绍

process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)

强调:
1. 需要使用关键字的方式来指定参数
2. args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号

参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,'egon',)
4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5 name为子进程的名称
1 p.start():启动进程,并调用该子进程中的p.run() 
2 p.run():进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法  
3 p.terminate():强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
4 p.is_alive():如果p仍然运行,返回True
5 p.join([timeout]):主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程  
方法介绍
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
5 p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)
属性介绍
在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。因此如果将process()直接写在文件中就会无限递归创建子进程报错。所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,就不会递归运行了。
在windows中使用process模块的注意事项

创建进程的两种方式

面向函数  及 面向对象

# import multiprocessing  # 这是个包
import os
from multiprocessing import Process

def son_process():
    '''这个函数中的代码是在子进程中执行的'''
    print('执行我啦',os.getpid(),os.getppid())

if __name__ == '__main__':
    # son_process的外面是一个主进程
    print('1 -->',os.getpid())
    p = Process(target=son_process)
    p.start()

执行结果:

  1 --> 6100
  执行我啦 18460 6100

import os
from multiprocessing import Process
class MyProcess(Process):
    def __init__(self,参数):
        super().__init__()
        self.一个属性 = 参数

    def run(self):
        print('子进程中要执行的代码')

if __name__ == '__main__':
    conn = '一个链接'
    mp = MyProcess(conn)
    mp.start()

 

进程模块

1.开启了一个子进程就已经实现了并发: 父进程(主进程)和子进程并发(同时执行)

import time
from multiprocessing import Process
def son_process():
    print('son start')
    time.sleep(1)
    print('son end')

if __name__ == '__main__':
    p = Process(target=son_process)
    p.start()
    for i in range(5):
        print('主进程')
        time.sleep(0.3)
执行结果:
主进程
son start
主进程
主进程
主进程
son end
主进程
异步执行

2.可以开启多个子进程么

import time
from multiprocessing import Process
def son_process():
    print('son start')
    time.sleep(1)
    print('son end')

if __name__ == '__main__':
    for i in range(3):
        p = Process(target=son_process)
        p.start()
    print("执行我了")

执行结果:
执行我了
son start
son start
son start
son end
son end
son end
开启多个子进程

3.可以给子进程中传参数么

import time
from multiprocessing import Process
def son_process(i):
    print('son start',i)
    time.sleep(1)
    print('son end',i)

if __name__ == '__main__':
    for i in range(3):
        p = Process(target=son_process,args=(i,))
        p.start()  # 通知操作系统 start并不意味着子进程已经开始了
args

4.多进程实现一个并发的socket server

import socket,time
from multiprocessing import Process
def talk(conn):
    conn, addr = sk.accept()
    print(conn)
    while True:
        msg = conn.recv(1024).decode()
        time.sleep(10)
        conn.send(msg.upper().encode())

if __name__ == '__main__':
    # 这句话下面的所有代码都只在主进程中执行
    sk = socket.socket()
    sk.bind(('127.0.0.1',9000))
    sk.listen()
    while True:
        conn,addr = sk.accept()
        Process(target=talk,args=(sk,)).start()
服务端
import socket

sk = socket.socket()




sk.connect(('127.0.0.1',9000))
while True:
    sk.send(b'hello')
    print(sk.recv(1024))
客户端

5.主进程和子进程之间的关系

import time
from multiprocessing import Process
def son_process(i):
    print('son start',i)
    time.sleep(1)
    print('son end',i)

if __name__ == '__main__':
    for i in range(5):
        p = Process(target=son_process,args=(i,))
        p.start()
    print('主进程的代码执行完毕')

执行结果:
主进程的代码执行完毕
son start 0
son start 2
son start 1
son start 3
son start 4
son end 0
son end 2
son end 1
son end 3
son end 4
关系代码
  • 主进程会等待子进程结束之后才结束
  • 原因是:父进程负责创建子进程,也负责回收子进程的资源

6.主进程可不可以直接结束一个子进程?

import time
from multiprocessing import Process
def son_process(i):
    while True:
        print('son start',i)
        time.sleep(0.5)
        print('son end',i)

if __name__ == '__main__':
    p = Process(target=son_process, args=(1,))
    p.start()           # 开启一个子进程,异步的
    print('主进程的代码执行完毕')
    print(p.is_alive())  # 子进程还活着
    p.terminate()        # 结束一个子进程,异步的,所以主进程还会在继续执行
    print(p.is_alive())  # 子进程还在活着 
    time.sleep(0.1)
    print(p.is_alive())

执行结果:
主进程的代码执行完毕
True
True
False
terminate

7.进程之间数据隔离的概念

import time
from multiprocessing import Process
n = [100]
def sub_n():
    global n  # 子进程对于主进程中的全局变量的修改是不生效的
    n.append(1)
    print('子进程n : ',n)
if __name__ == '__main__':
    p = Process(target = sub_n)
    p.start()
    p.join()     # 阻塞 直到子进程p结束
    print('主进程n : ',n)

执行结果:
子进程n :  [100, 1]
主进程n :  [100]
数据隔离例子

8.开启十个进程执行subn

import time
from multiprocessing import Process
n = [100]
import random
def sub_n():
    global n  # 子进程对于主进程中的全局变量的修改是不生效的
    time.sleep(random.random())
    n.append(1)
    print('子进程n : ',n)
if __name__ == '__main__':
    p_lst = []
    for i in range(10):
        p = Process(target = sub_n)
        p.start()
        p_lst.append(p)
    for p in p_lst:p.join()  # 阻塞 只有一个条件是能够让我继续执行 这个条件就是子进程结束
    print('主进程n : ',n)

执行结果:
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
子进程n :  [100, 1]
主进程n :  [100]
例子

9.join的扩展

import time
from multiprocessing import Process
n = [100]
def sub_n():
    global n  # 子进程对于主进程中的全局变量的修改是不生效的
    n.append(1)
    print('子进程n : ',n)
    time.sleep(10)
    print('子进程结束')

if __name__ == '__main__':
    p = Process(target = sub_n)
    p.start()
    p.join(timeout = 5)     # 如果不设置超时时间 join会阻塞直到子进程p结束
    # timeout超时
    # 如果设置的超时时间,那么意味着如果不足5s子进程结束了,程序结束阻塞
    # 如果超过5s还没有结束,那么也结束阻塞
    print('主进程n : ',n)
    p.terminate()  # 也可以强制结束一个子进程
join 拓展

守护进程

守护进程

  • 守护进程会等待主进程的代码结束而结束,不会等待其他子进程的结束
  • 要想守护进程等待其他子进程,只需要在主进程中加上join
import time
from multiprocessing import Process
def alive():
    while True:
        print('连接监控程序,并且发送报活信息')
        time.sleep(0.6)

def func():
    '主进程中的核心代码'
    while True:
        print('选择的项目')
        time.sleep(1)
        print('根据用户的选择做一些事儿')

if __name__ == '__main__':
    p = Process(target=alive)
    p.daemon = True   # 必须放在 start之前,设置子进程为守护进程,守护进程会随着主进程代码的结束而结束
    p.start()
    p = Process(target=func)
    p.start()
    p.join()   # 在主进程中等待子进程结束,守护进程就可以帮助守护其他子进程了
执行结果:

连接监控程序,并且发送报活信息
选择的项目
连接监控程序,并且发送报活信息
根据用户的选择做一些事儿
选择的项目
连接监控程序,并且发送报活信息
连接监控程序,并且发送报活信息
根据用户的选择做一些事儿
选择的项目......

锁lock

import json
import time
from multiprocessing import Process,Lock

def search(name):
    '''查询余票的功能'''
    with open('ticket') as f:
        dic = json.load(f)
        print(name , dic['count'])

def buy(name):
    with open('ticket') as f:
        dic = json.load(f)
    time.sleep(0.1)
    if dic['count'] > 0:
        print(name,'买到票了')
        dic['count'] -=1
    time.sleep(0.1)
    with open('ticket','w') as f:
        json.dump(dic,f)

def get_ticket(name,lock):
    search(name)
    lock.acquire()  # 只有第一个到达的进程才能获取锁,剩下的其他人都需要在这里阻塞
    buy(name)
    lock.release()  # 有一个人还锁,会有一个人再结束阻塞拿到钥匙

if __name__ == '__main__':
    lock = Lock()
    for i in range(10):
        p = Process(target=get_ticket,args=('name%s'%i,lock))
        p.start()

信号量 Semaphore

信号量的本质 : 锁 + 计数器实现的

import time
import random
from multiprocessing import Process,Semaphore
def ktv(name,sem):
    sem.acquire()
    print("%s走进了ktv"%name)
    time.sleep(random.randint(5,10))
    print("%s走出了ktv" % name)
    sem.release()

if __name__ == '__main__':
    sem = Semaphore(4)
    for i in range(100):
        p = Process(target=ktv,args = ('name%s'%i,sem))
        p.start()

事件 Event

from multiprocessing import Event
Event 事件类
e = Event()
e 事件对象
事件本身就带着标识 : False
wait 阻塞

  • 它的阻塞条件是 对象标识为False
  • 结束阻塞条件是 对象标识为True

对象的标识相关的 :

  • set 将对象的标识设置为True
  • clear 将对象的标识设置为False
  • is_set 查看对象的标识是否为True
import time
import random
from multiprocessing import Event,Process
def traffic_light(e):
    print('\033[1;31m红灯亮\033[0m')
    while True:
        time.sleep(2)
        if e.is_set():   # 如果当前是绿灯
            print('\033[1;31m红灯亮\033[0m') # 先打印红灯亮
            e.clear()                        # 再把灯改成红色
        else :           # 当前是红灯
            print('\033[1;32m绿灯亮\033[0m') # 先打印绿灯亮
            e.set()                          # 再把灯变绿色

def car(e,carname):
    if not e.is_set():
        print('%s正在等待通过'%carname)
        e.wait()
    print('%s正在通过'%carname)

if __name__ == '__main__':
    e = Event()
    p = Process(target=traffic_light,args = (e,))
    p.start()
    for i in range(100):
        time.sleep(random.randrange(0,3))
        p = Process(target=car, args=(e,'car%s'%i))
        p.start()
红绿灯

进程之间的通信-IPC ( Inter-Process Communication)

实现进程之间通信的两种机制:

  • 管道 Pipe
  • 队列 Queue
from multiprocessing import Queue,Process
def consumer(q):
    print( '子进程 :', q.get())

if __name__ == '__main__':
    q = Queue()
    p = Process(target=consumer,args=(q,))
    p.start()
    q.put('hello,world')

执行结果:
子进程 : hello,world
import time
from multiprocessing import Queue,Process

def producer(name,food,num,q):
    '''生产者'''
    for i in range(num):
        time.sleep(0.3)
        foodi = food + str(i)
        print('%s生产了%s'%(name,foodi))
        q.put(foodi)

def consumer(name,q):
    while True:
        food = q.get()   # 等待接收数据
        if food == None:break
        print('%s吃了%s'%(name,food))
        time.sleep(1)

if __name__ == '__main__':
    q = Queue(maxsize=10)
    p1 = Process(target=producer,args = ('宝元','泔水',20,q))
    p2 = Process(target=producer,args = ('战山','鱼刺',10,q))
    c1 = Process(target=consumer, args=('alex', q))
    c2 = Process(target=consumer, args=('wusir', q))
    p1.start()   # 开始生产
    p2.start()   # 开始生产
    c1.start()
    c2.start()
    p1.join()    # 生产者结束生产了
    p2.join()    # 生产者结束生产了
    q.put(None)  # put None 操作永远放在所有的生产者结束生产之后
    q.put(None)  # 有几个消费者 就put多少个None
生产者消费者模型
import  time
from multiprocessing import JoinableQueue,Process

def consumer(name,q):
    while True:
        food = q.get()
        time.sleep(1)
        print('%s消费了%s'%(name,food))
        q.task_done()

def producer(name,food,num,q):
    '''生产者'''
    for i in range(num):
        time.sleep(0.3)
        foodi = food + str(i)
        print('%s生产了%s'%(name,foodi))
        q.put(foodi)
    q.join()   # 消费者消费完毕之后会结束阻塞
if __name__ == '__main__':
    q = JoinableQueue()
    p1 = Process(target=producer, args=('宝元', '泔水', 20, q))
    c1 = Process(target=consumer, args=('alex', q))
    c2 = Process(target=consumer, args=('wusir', q))
    c1.daemon = True
    c2.daemon = True
    p1.start()
    c1.start()
    c2.start()
    p1.join()
JoinableQueue实现的生产者消费者模型

manager数据共享

Manager是一个类 内部有一些数据类型能够实现进程之间的数据共享
dict list这样的数据 内部的数字进行自加 自减 是会引起数据不安全的,这种情况下 需要我们手动加锁完成
因此 我们一般情况下 不适用这种方式来进行进程之间的通信
我们宁可使用Queue队列或者其他消息中间件 来实现消息的传递 保证数据的安全

from multiprocessing import Manager,Process,Lock
def work(d,lock):
    # with lock: #不加锁而操作共享的数据,肯定会出现数据错乱
    #     d['count']-=1
    lock.acquire()
    d['count'] -= 1
    lock.release()

if __name__ == '__main__':
    lock=Lock()
    m = Manager()
    dic=m.dict({'count':100})
    p_l=[]
    for i in range(100):
        p=Process(target=work,args=(dic,lock))
        p_l.append(p)
        p.start()
    for p in p_l:
        p.join()
    print(dic)
例子

进程池

  • 固定的几个进程 执行无限的任务

为什么要有进程池?进程池的概念。

在程序实际处理问题过程中,忙时会有成千上万的任务需要被执行,闲时可能只有零星任务。那么在成千上万个任务需要被执行的时候,我们就需要去创建成千上万个进程么?首先,创建进程需要消耗时间,销毁进程也需要消耗时间。第二即便开启了成千上万的进程,操作系统也不能让他们同时执行,这样反而会影响程序的效率。因此我们不能无限制的根据任务开启或者结束进程。那么我们要怎么做呢?

在这里,要给大家介绍一个进程池的概念,定义一个池子,在里面放上固定数量的进程,有需求来了,就拿一个池中的进程来处理任务,等到处理完毕,进程并不关闭,而是将进程再放回进程池中继续等待任务。如果有很多任务需要执行,池中的进程数量不够,任务就要等待之前的进程执行任务完毕归来,拿到空闲进程才能继续执行。也就是说,池中进程的数量是固定的,那么同一时间最多有固定数量的进程在运行。这样不会增加操作系统的调度难度,还节省了开闭进程的时间,也一定程度上能够实现并发效果。

multiprocess.Pool模块

概念介绍

Pool([numprocess  [,initializer [, initargs]]]):创建进程池
1 numprocess:要创建的进程数,如果省略,将默认使用cpu_count()的值
2 initializer:是每个工作进程启动时要执行的可调用对象,默认为None
3 initargs:是要传给initializer的参数组
参数介绍
1 p.apply(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
2 '''需要强调的是:此操作并不会在所有池工作进程中并执行func函数。如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()'''
3 
4 p.apply_async(func [, args [, kwargs]]):在一个池工作进程中执行func(*args,**kwargs),然后返回结果。
5 '''此方法的结果是AsyncResult类的实例,callback是可调用对象,接收输入参数。当func的结果变为可用时,将理解传递给callback。callback禁止执行任何阻塞操作,否则将接收其他异步操作中的结果。'''
6    
7 p.close():关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
8 
9 P.jion():等待所有工作进程退出。此方法只能在close()或teminate()之后调用
主要方法
1 方法apply_async()和map_async()的返回值是AsyncResul的实例obj。实例具有以下方法
2 obj.get():返回结果,如果有必要则等待结果到达。timeout是可选的。如果在指定时间内还没有到达,将引发一场。如果远程操作中引发了异常,它将在调用此方法时再次被引发。
3 obj.ready():如果调用完成,返回True
4 obj.successful():如果调用完成且没有引发异常,返回True,如果在结果就绪之前调用此方法,引发异常
5 obj.wait([timeout]):等待结果变为可用。
6 obj.terminate():立即终止所有工作进程,同时不执行任何清理或结束任何挂起工作。如果p被垃圾回收,将自动调用此函数
其他方法

map用法

from multiprocessing import Pool  #
def func(i):
    i -= 1
    return i**2

# 你的池中打算放多少个进程,个数cpu的个数 * 1|2
if __name__ == '__main__':
    p = Pool(5)
    ret = p.map(func,range(100))  # 自动带join
    print(ret)

执行结果:
[1, 0, 1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400, 441, 484, 529, 576, 625, 676, 729, 784, 841, 900, 961, 1024, 1089, 1156, 1225, 1296, 1369, 1444, 1521, 1600, 1681, 1764, 1849, 1936, 2025, 2116, 2209, 2304, 2401, 2500, 2601, 2704, 2809, 2916, 3025, 3136, 3249, 3364, 3481, 3600, 3721, 3844, 3969, 4096, 4225, 4356, 4489, 4624, 4761, 4900, 5041, 5184, 5329, 5476, 5625, 5776, 5929, 6084, 6241, 6400, 6561, 6724, 6889, 7056, 7225, 7396, 7569, 7744, 7921, 8100, 8281, 8464, 8649, 8836, 9025, 9216, 9409, 9604]
map用法

同步方式向进程池提交任务 - apply

import time
from multiprocessing import Pool  #
def func(i):
    i -= 1
    time.sleep(0.5)
    return i**2

# 你的池中打算放多少个进程,个数cpu的个数 * 1|2
if __name__ == '__main__':
    p = Pool(5)
    for i in range(10):
        ret = p.apply(func,args=(i,))  # 自动带join  串行 同步  apply就是同步提交任务
        print(ret)

执行结果:
1
0
1
4
9
16
25
36
49
64
apply的同步提交

异步方式向进程池提交任务  - apply_async

 

import time
from multiprocessing import Pool  #
def func(i):
    i -= 1
    time.sleep(1)
    return i**2
if __name__ == '__main__':
    p = Pool(5)
    l = []
    for i in range(10):
        ret = p.apply_async(func,args=(i,))  # 自动带join  异步的  apply_async异步提交任务
        l.append(ret)
    for ret in l:
        print(ret.get())
apply_async 异步提交,并接受返回值

 

总结以上:

 

什么是进程池?    有限的进程的池子
为什么要用进程池?

  • 任务很多 cpu个数*5个任务以上
  • 为了节省创建和销毁进程的时间 和 操作系统的资源

一般进程池中进程的个数:

  • cpu的1-2倍
  •  如果是高计算,完全没有io,那么就用cpu的个数
  • 随着IO操作越多,可能池中的进程个数也可以相应增加

向进程池中提交任务的三种方式

  • map 异步提交任务 简便算法 接收的参数必须是 子进程要执行的func,可迭代的(可迭代中的每一项都会作为参数被传递给子进程)
    •   能够传递的参数是有限的,所以比起apply_async限制性比较强
  • apply 同步提交任务(你删了吧)
  • apply_async 异步提交任务
    • 能够传递比map更丰富的参数,但是比较麻烦
    • 首先 apply_async提交的任务和主进程完全异步
    • 可以通过先close进程池,再join进程池的方式,强制主进程等待进程池中任务的完成
    • 也可以通过get获取返回值的方式,来等待任务的返回值
    • 我们不能在apply_async提交任务之后直接get获取返回值

回调函数

  • 主动执行func,然后在func执行完毕之后的返回值,直接传递给back_func作为参数,调用back_func
  • 处理池中任务的返回值
  • 回调函数是由谁执行的? 主进程
import os
import time
import random
from multiprocessing import Pool  #
def func(i):     # [2,1,1,5,0,0.2]
    i -= 1
    time.sleep(random.uniform(0,2))
    return i**2

def back_func(args):
    print(args,os.getpid())

if __name__ == '__main__':
    print(os.getpid())
    p = Pool(5)
    l = []
    for i in range(10):
        ret = p.apply_async(func,args=(i,),callback=back_func)  # 5个任务
    p.close()
    p.join()
执行结果:

18656
9 18656
0 18656
16 18656
1 18656
4 18656
1 18656
25 18656
36 18656
64 18656
49 18656

 
# import re
# import json
# from urllib.request import urlopen
# from multiprocessing import Pool
#
# def get_page(i):
#     ret = urlopen('https://movie.douban.com/top250?start=%s&filter='%i).read()
#     ret = ret.decode('utf-8')
#     return ret
#
# def parser_page(s):
#     com = re.compile(
#         '<div class="item">.*?<div class="pic">.*?<em .*?>(?P<id>\d+).*?<span class="title">(?P<title>.*?)</span>'
#         '.*?<span class="rating_num" .*?>(?P<rating_num>.*?)</span>.*?<span>(?P<comment_num>.*?)评价</span>', re.S)
#
#     ret = com.finditer(s)
#     with open('file','a',encoding='utf-8') as f:
#         for i in ret:
#             dic = {
#                 "id": i.group("id"),
#                 "title": i.group("title"),
#                 "rating_num": i.group("rating_num"),
#                 "comment_num": i.group("comment_num"),
#             }
#             f.write(json.dumps(dic,ensure_ascii=False)+'\n')
#
# if __name__ == '__main__':
#     p = Pool(5)
#     count = 0
#     for i in range(10):
#         p.apply_async(get_page,args=(count,),callback=parser_page)
#         count += 25
#     p.close()
#     p.join()


# import json
# with open('file2','w',encoding='utf-8') as f:
#     json.dump({'你好':'alex'},f,ensure_ascii=False)
爬虫实例

 

 

posted @ 2019-02-09 02:22  小萍瓶盖儿  阅读(398)  评论(0编辑  收藏  举报