python进程

送分小仙女□ 提交于 2019-12-13 15:26:37

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。

广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

进程概念

  • 进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
  • 进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。
  • 进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

进程的作用

  • 为了提高资源利用率和系统处理能力,现阶段计算机系统都是多道程序系统,即多道程序并发执行。
  • 优化系统资源,方便计算机调度,避免系统运算紊乱。
  • 进程是一种数据结构,能够清晰的刻画动态系统的内在规律,增加程序运行时的动态性。

进程特征

  • 动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
  • 并发性:任何进程都可以同其他进程一起并发执行。
  • 独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
  • 异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进。

进程调度

  • 先来先服务(FCFS)调度算法: 是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
  • 短作业(进程)优先调度算法(SJ/PF)是指对短作业或短进程优先调度的算法,该算法既可用于作业调度,也可用于进程调度。但其对长作业不利;不能保证紧迫性作业(进程)被及时处理;作业的长短只是被估算出来的。
  • 时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。
  • 多级反馈队列调度算法则不必事先知道各种进程所需的执行时间,而且还可以满足各种类型进程的需要,因而它是目前被公认的一种较好的进程调度算法。

进程的并行与并发

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

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

python程序中的进程操作

运行中的程序就是一个进程。所有的进程都是通过它的父进程来创建的。因此,运行起来的python程序也是一个进程,那么我们也可以在程序中再创建进程。多个进程可以实现并发效果,也就是说,当我们的程序中存在多个进程的时候,在某些时候,就会让程序的执行速度变快。

multiprocess模块

仔细说来,multiprocess不是一个模块而是python中一个操作、管理进程的包。 在这个包中几乎包含了和进程有关的所有子模块。大致分为四个部分:创建进程部分、进程同步部分、进程池部分、进程之间数据共享。


multiprocess.process模块

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.join只能join住start开启的进程,而不能join住run开启的进程 

属性介绍:
p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
p.name:进程的名称
p.pid:进程的pid
p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
p.authkey:进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

使用process模块创建进程

import os
from multiprocessing import Process

def f(x):
    print('子进程id :{} :{}'.format(os.getpid(), x*x))

if __name__ == '__main__':
    print('主进程id :{}'.format(os.getpid()))
    p_lst = []
    for i in range(5):
        p = Process(target=f, args=(i,))
        p.start()
        p_lst.append(p)
    #多个进程同时进行是,这种join写法进程是并行的
    for p in p_lst:
        p.join()#主线程等待p终止
    print ('我是父进程')
	
输出:
主进程id :61688
子进程id :60160 :1
子进程id :62188 :4
子进程id :60804 :0
子进程id :55964 :9
子进程id :56200 :16
我是父进程

以继承Process类的形式开启进程

import os
from multiprocessing import Process
class MyProcess(Process):
    def __init__(self,name):
        Process.__init__(self)
        self.name=name
    def run(self):
        print('{}正在和女主播聊天: pid:{}'.format(self.name, os.getpid()))

if __name__ == '__main__':
    p1 = MyProcess('wupeiqi')
    p2 = MyProcess('yuanhao')
    p3 = MyProcess('nezha')

    p1.start() #start会自动调用run
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()
    print('主线程 pid:{}'.format(os.getpid()))
	
输出:
wupeiqi正在和女主播聊天: pid:59160
yuanhao正在和女主播聊天: pid:23096
nezha正在和女主播聊天: pid:54352
主线程 pid:56472

进程之间的数据隔离问题

from multiprocessing import Process
def work():
    global n
    n=0
    print('子进程内:{} '.format(n))

if __name__ == '__main__':
    n = 100
    p=Process(target=work)
    p.start()
    print('主进程内:{} '.format(n))
	
print:
主进程内:100 
子进程内:0 

守护进程

创建守护进程,会随着主进程的结束而结束

  •   其一:守护进程会在主进程代码执行结束后终止(非守护进程在主进程结束之后仍然会继续运行)

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

import time
from multiprocessing import Process

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()
    time.sleep(0.1)
    # 打印该行则主进程代码结束,则守护进程p1应该被终止.#可能会有p1任务执行的打印信息123,因为主进程打印main----时,p1也执行了,但是随即被终止.
    print("main-------")  
print:
123
456
main-------
end456


进程同步(multiprocess.Lock)

尽管并发编程让我们能更加充分的利用IO资源,但是也给我们带来了新的问题。当多个进程使用同一份数据资源的时候,就会引发数据安全或顺序混乱问题。

# 由并发变成了串行,牺牲了运行效率,但避免了竞争
import os
import time
import random
from multiprocessing import Process,Lock

def work(lock,n):
    lock.acquire()
    print('%s: %s is running' % (n, os.getpid()))
    time.sleep(random.random())
    print('%s: %s is done' % (n, os.getpid()))
    lock.release()
if __name__ == '__main__':
    lock=Lock()
    for i in range(3):
        p=Process(target=work,args=(lock,i))
        p.start()

print:
0: 62176 is running
0: 62176 is done
1: 4300 is running
1: 4300 is done
2: 2932 is running
2: 2932 is done

进程间通信——队列(multiprocess.Queue)

创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递。

Queue([maxsize]) 
创建共享的进程队列。maxsize是队列中允许的最大项数。如果省略此参数,则无大小限制。底层队列使用管道和锁定实现。另外,还需要运行支持线程以便队列中的数据传输到底层管道中。


Queue的实例q具有以下方法:

q.get( [ block [ ,timeout ] ] ) 
返回q中的一个项目。如果q为空,此方法将阻塞,直到队列中有项目可用为止。block用于控制阻塞行为,默认为True. 如果设置为False,将引发Queue.Empty异常(定义在Queue模块中)。timeout是可选超时时间,用在阻塞模式中。如果在制定的时间间隔内没有项目变为可用,将引发Queue.Empty异常。

q.get_nowait( ) 
同q.get(False)方法。
q.put(item [, block [,timeout ] ] ) 
将item放入队列。如果队列已满,此方法将阻塞至有空间可用为止。block控制阻塞行为,默认为True。如果设置为False,将引发Queue.Empty异常(定义在Queue库模块中)。timeout指定在阻塞模式中等待可用空间的时间长短。超时后将引发Queue.Full异常。

q.qsize() 
返回队列中目前项目的正确数量。此函数的结果并不可靠,因为在返回结果和在稍后程序中使用结果之间,队列中可能添加或删除了项目。在某些系统上,此方法可能引发NotImplementedError异常。


q.empty() 
如果调用此方法时 q为空,返回True。如果其他进程或线程正在往队列中添加项目,结果是不可靠的。也就是说,在返回和使用结果之间,队列中可能已经加入新的项目。

q.full() 
如果q已满,返回为True. 由于线程的存在,结果也可能是不可靠的(参考q.empty()方法)。。



基于队列实现生产者消费者模型

from multiprocessing import Process,Queue
import time,random,os
#多个消费者的例子:有几个消费者就需要发送几次结束信号
def consumer(q):
    while True:
        res=q.get()
        if res is None:break #收到结束信号则结束
        time.sleep(random.randint(1,3))
        print('{} 吃 {}'.format(os.getpid(),res))

def producer(q):
    for i in range(2):
        time.sleep(random.randint(1,3))
        res='包子%s' %i
        q.put(res)
        print('{} 生产了 {}'.format(os.getpid(),res))

if __name__ == '__main__':
    q=Queue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=(q,))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))

    #开始
    p1.start()
    c1.start()

    p1.join()
    q.put(None) #发送结束信号    取决于有多少个消费者
    print('主进程结束')
	
print:
63464 生产了 包子0
63464 生产了 包子1
主进程结束
5240 吃 包子0
5240 吃 包子1

可连接的共享进程队列JoinableQueue([maxsize])

JoinableQueue的实例p除了与Queue对象相同的方法之外,还具有以下方法:

q.task_done() 
使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。

q.join() 
生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。 
下面的例子说明如何建立永远运行的进程,使用和处理队列上的项目。生产者将项目放入队列,并等待它们被处理。

JoinableQueue实现生产消费模型

from multiprocessing import Process, JoinableQueue
import time, random, os

def consumer(q):
    while True:
        res = q.get()
        time.sleep(random.randint(1, 3))
        print('{} 吃 {}'.format(os.getpid(), res))
        q.task_done()  # 向q.join()发送一次信号,证明一个数据已经被取走了

def producer(name, q):
    for i in range(10):
        time.sleep(random.randint(1, 3))
        res = '%s%s' % (name, i)
        q.put(res)
        print('{} 生产了 {}'.format(os.getpid(), res))
    q.join()  # 生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。

if __name__ == '__main__':
    q = JoinableQueue()
    # 生产者们:即厨师们
    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
    c2.daemon = True

    # 开始
    p_l = [p1, p2, p3, c1, c2]
    for p in p_l:
        p.start()

    p1.join()
    p2.join()
    p3.join()
    print('主')

    # 主进程等--->p1,p2,p3等---->c1,c2
    # p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
    # 因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。


multiprocessing.Pool进程池

创建的子进程数量多时,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。

初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会用之前的进程来执行新的任务。

multiprocessing.Pool常用函数解析:

apply_async(func[, args[, kwds]]) :使用非阻塞方式调用func(并行执行,堵塞方式必须等待上一个进程退出才能执行下一个进程),args为传递给func的参数列表,kwds为传递给func的关键字参数列表;
close():关闭Pool,使其不再接受新的任务;
terminate():不管任务是否完成,立即终止;
join():主进程阻塞,等待子进程的退出, 必须在close或terminate之后使用;

下面我们看一个简单的multiprocessing.Pool类的实例:

import multiprocessing
import time
import os
import random
def test1(msg):
    t_start = time.time()
    print("%s开始执行,进程号为%d" % (msg, os.getpid()))
    time.sleep(random.random() * 2)
    t_stop = time.time()
    print("%s执行完成,耗时%.2f" % (msg, t_stop - t_start))


if __name__ == "__main__":

    po = multiprocessing.Pool(3)
    for i in range(0, 10):
        # Pool().apply_async(要调用的目标,(传递给目标的参数元祖,))
        # 每次循环将会用空闲出来的子进程去调用目标
        po.apply_async(test1, (i,))

    print("-----start-----")
    po.close()  # 关闭进程池,关闭后po不再接收新的请求
    po.join()  # 等待po中所有子进程执行完成,必须放在close语句之后
    print("-----end-----")
	
print:
-----start-----
0开始执行,进程号为62592
1开始执行,进程号为52116
2开始执行,进程号为2232
2执行完成,耗时0.69
3开始执行,进程号为2232
3执行完成,耗时0.31
4开始执行,进程号为2232
4执行完成,耗时0.13
5开始执行,进程号为2232
0执行完成,耗时1.63
6开始执行,进程号为62592
6执行完成,耗时0.01
7开始执行,进程号为62592
1执行完成,耗时1.86
8开始执行,进程号为52116
5执行完成,耗时1.76
9开始执行,进程号为2232
7执行完成,耗时1.80
8执行完成,耗时1.83
9执行完成,耗时1.77
-----end-----

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!