并发编程——进程

什么是进程:

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

  狭义定义:进程是正在运行的程序。

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

第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3] 
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
进程的概念
动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。
进程的特征
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。
进程与程序中的区别

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

进程调度:

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

先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。FCFS算法比较有利于长作业(进程),而不利于短作业(进程)。由此可知,本算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业(进程)。
先来先服务调度算法
      时间片轮转(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队列的末尾,把处理机分配给新到的高优先权进程。
多级反馈队列
import os
import time
print(os.getpid())  # 进程的id   4268
time.sleep(100)
print(os.getpid())  # 4268
# 同一次的进程id不会改变,直到再次运行。
# 每个进程都有自己被系统随机分配的id.
如何表现出进程

进程的创建与结束:

  进程的创建:

    1,系统初始化。

    2,一个进程在运行过程中开启了子进程。

    3,用户的交互式请求,而创建一个新进程。(如用户双击一个软件)

    4,一个批处理作业的初始化。

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

import os
import time
from multiprocessing import Process

def process1():
    print('process1:',os.getpid())  # process1: 11948
    time.sleep(10)

if __name__ == '__main__':  # 在windows系统的环境下,必须有这条代码,否则会报错。
    print(os.getpid())  # 6792  
    p = Process(target=process1) # 相当于实例化一个对象
    p.start()   # 启动

# 可看出得到的id并不同,所以是新创建了一个新进程。
创建一个新进程
import os
import time
from multiprocessing import Process

def process1(n,name,num = 10,):
    print('process1:',os.getpid())  # process1: 7876
    print(n,name,num)   # 1 alex 10
    time.sleep(10)

if __name__ == '__main__':
    print(os.getpid())  # 12160
    p = Process(target=process1,args = (1,'alex'))
    p.start()
可以传参数的新进程

  进程的结束:

    1,正常退出(自愿,如用户点击交互式页面的叉号)

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

    3,严重错误(非自愿,执行非法指令,如引用不存在的内存)

    4,被其他进程杀死(非资源,如用户点击终止程序按键)

主进程和子进程:

import os
import time
from multiprocessing import Process

def func():
    print(os.getpid(),os.getppid())
    time.sleep(1)

if __name__ == '__main__':
    print(os.getpid(),os.getppid())
    p = Process(target=func)
    p.start()
    print('*'*20)
    time.sleep(0.5)
    print('*'*40)
    
# 主进程默认会等待子进程执行完毕之后才结束。
# 主进程和子进程的代码是异步的。
# 主进程等待子进程结束才结束是为了回收一些子进程的资源。
# 开启一个进程是有时间开销的:操作系统相应开启进程指令,给这个进程分配必要的资源。

对子进程进行同步控制:

  join方法:

import os
from multiprocessing import Process

def func(exp):
    print(os.getpid(),os.getppid())
    result = eval(exp)
    # print(exp)
    with open('file','w') as f:
        f.write(str(result))

if __name__ == '__main__':
    print(os.getpid(),os.getppid())
    p = Process(target=func,args=('3*2',))
    p.start()
    ret = 3*5
    p.join()
    with open('file') as f:
        content = f.read()
    ret = ret + int(content)
    print(ret)
同步控制

开启多个进程:

import os
import time
from multiprocessing import Process

def process(n):
    print(os.getpid(),os.getppid())
    time.sleep(1)
    print(n)

if __name__ == '__main__':
    p_lst = []
    for i in range(10):
        p = Process(target=process,args=(i,))
        p.start()
        p_lst.append(p)
    for p in p_lst:p.join() # 检测p是否结束,如果没有结束就阻塞直到结束,如果已经结束就不阻塞。
    print('求和')

另外一种继承Process类的形式开启进程的方式:

import os
from multiprocessing import Process
class Myprocess(Process):
    def __init__(self,*args):
        super().__init__()
        self.args = args
    def run(self):
        print(os.getpid(),self.name,self.pid)
        for name in self.args:
            print('%s和女主播聊天'%name)

if __name__ == '__main__':
    print(os.getpid(),os.getppid())
    p = Myprocess('alex','wusir')
    p.start()   # 在执行start的时候,会自动执行run方法。
开启进程的第二种方式

进程之间的数据隔离问题:

from multiprocessing import Process

n = 100
def func():
    global n
    n += 1
    print('son :',n)    # son : 101

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

# 子程序的变量改变无法影响到主程序的变量。
进程中的数据隔离

守护进程:

会随着主进程的结束而结束。

主进程创建守护进程。

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

  其二:守护进程内无法再开启子进程,否则抛出异常:

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

import time
from multiprocessing import Process

def func1():
    print('in func1')
    while True:
        time.sleep(1)
        print('func1')

def func2():
    print('in func2')
    time.sleep(5)
    print('func2')

if __name__ == '__main__':
    p1 = Process(target=func1)
    # 在一个进程开启之前可以设置它为一个守护进程
    p1.daemon = True
    p1.start()
    p2 = Process(target=func2)
    p2.start()
    time.sleep(2)
    print('在主进程中')

# 主进程的代码,大概在2s多的时候就结束了。
# p2子进程是在5s多的时候结束。
# 主进程结束
# p1是在主进程的代码执行完毕之后就结束了。
守护进程

进程中的其中方法:

import time
from multiprocessing import Process

def func():
    print('wahaha')
    time.sleep(10)
    print('wahaha end')

if __name__ == '__main__':
    p = Process(target=func)
    p.start()
    print(p.is_alive())
    time.sleep(1)
    p.terminate()
    print(p.is_alive())
    time.sleep(0.5)
    print(p.is_alive())

 

import time
import random
from multiprocessing import Process
class Myprocess(Process):
    def __init__(self,*args):
        super().__init__()
        self.args = args
    def run(self):
        for i in self.args:
        # print('%s正在和网红聊天'% self.name)
            time.sleep(random.randrange(1,5))
            # print('%s正在和网红聊天'% self.name)
            print('%s正在和网红聊天'% i)

if __name__ == '__main__':
    p1 = Myprocess('alex','wusir')
    p1.start()
    print(p1.pid)
    print(p1.name)
进程对象的其他属性:pid和name

 

posted @ 2018-05-10 17:56  Qingqiu_Gu  阅读(163)  评论(0编辑  收藏  举报