Python 进程和线程

1、进程

1)进程(process),是计算机中已运行程序的实体。进程为曾经是分时系统的基本运作单位。在面向进程设计的系统中,进程是程序的基本执行实体;在面向线程设计的系统中,进程本身不是基本运行单位,而是线程的容器。程序本身只是指令、数据及其组织形式的描述,进程才是程序的真正运行实例。若干进程有可能与同一个程序相关系,且每个进程皆可以同步或异步的方式独立运行。现代计算机系统可在同一段时间内以进程的形式将多个程序加载到存储器中,并借由时间共享,以在一个处理器上表现出同时(平行性)运行的感觉。

2)进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。 

3)动态性:进程的实质是程序在多道程序系统中的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行
独立性:进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位;
异步性:由于进程间的相互制约,使进程具有执行的间断性,即进程按各自独立的、不可预知的速度向前推进
结构特征:进程由程序、数据和进程控制块三部分组成。
多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。 

2、进程调度

1)在多进程、多线程并发的环境里,从概念上看,有多个进程或者多个线程在同时执行,具体到单个CPU级别,实际上任何时刻只能有一个进程或者线程处于执行状态;因此OS需要决定哪个进程执行,哪些进程等待,也就是进程的调度。

 2)程序使用CPU的三种模式:IO密集型、计算密集型和平衡型。对于IO密集型程序来说,响应时间非常重要;对于CPU密集型来说,CPU的周转时间就比较重要;对于平衡型程序来说,响应和周转之间的平衡是最重要的。

 3)CPU的调度就是要达到极小化平均响应时间、极大化系统吞吐率、保持系统各个功能部件均处于繁忙状态和提供某种公平的机制。

4)对于实时系统来说,调度的目标就是要达到截止时间前完成所应该完成的任务和提供性能的可预测性。 

3、调度算法 (参考

【3-1】先来先服务调度算法

1)FCFS(First come first serve),或者称为FIFO算法,先来先服务(FCFS)调度算法是一种最简单的调度算法,该算法既可用于作业调度,也可用于进程调度。当在作业调度中采用该算法时,每次调度都是从后备作业队列中选择一个或多个最先进入该队列的作业,将它们调入内存,为它们分配资源、创建进程,然后放入就绪队列。在进程调度中采用FCFS算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配处理机,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机。该算法适合于CPU繁忙型作业,而不利于I/O繁忙型的作业。

【3-2】短作业(进程)优先调度算法

1)短作业(进程)优先调度算法SJPF,是指对短作业或短进程优先调度的算法。它们可以分别用于作业调度和进程调度。短作业优先(SJPF)的调度算法是从后备队列中选择一个或若干个估计运行时间最短的作业,将它们调入内存运行。而短进程优先(SJPF)调度算法则是从就绪队列中选出一个估计运行时间最短的进程,将处理机分配给它,使它立即执行并一直执行到完成,或发生某事件而被阻塞放弃处理机时再重新调度。 

2)短作业(进程)优先算法的缺点:
       ①必须知道作业的运行时间,在采用这种算法时,要先知道每个作业运行时间。即使是程序员也很难估计作业的运行时间,如果估计过低,系统就可能按照估计的时间终止作业的运行,但此时作业并没有完成,故一般都会偏长估计。
       ②对长作业非常不利,长作业的周转时间会明显的增长。
       ③在采用SJPF算法时,人机无法实现交互。
       ④该调度算法完全未考虑作业的紧迫程度,故不能保证紧迫性作业能够得到及时处理。 

 【3-3】优先权调度算法

1)为了照顾紧迫型作业,使之在进入系统后便获得优先处理,引入了最高优先权优先(FPF)调度算法。此算法常被用于批处理系统中,作为作业调度算法,也作为多种操作系统中的进程调度算法,还可用于实时系统中。当把该算法用于作业调度时,系统将从后备队列中选择若干个优先权最高的作业装入内存。当用于进程调度时,该算法是把处理机分配给就绪队列中优先权最高的进程,这时,又可进一步把该算法分成如下两种: 

   非抢占式优先权算法  

1)在这种方式下,系统一旦把处理机分配给就绪队列中优先权最高的进程后,该进程便一直执行下去,直至完成;或因发生某事件使该进程放弃处理机时,系统方可再将处理机重新分配给另一优先权最高的进程。这种调度算法主要用于批处理系统中;也可用于某些对实时性要求不严的实时系统中。

   抢占式优先权调度算法

1)在这种方式下,系统同样是把处理机分配给优先权最高的进程,使之执行。但在其执行期间,只要又出现了另一个其优先权更高的进程,进程调度程序就立即停止当前进程(原优先权最高的进程)的执行,重新将处理机分配给新到的优先权最高的进程。因此,在采用这种调度算法时,是每当系统中出现一个新的就绪进程i 时,就将其优先权Pi与正在执行的进程j 的优先权Pj进行比较。如果Pi≤Pj,原进程Pj便继续执行;但如果是Pi>Pj,则立即停止Pj的执行,做进程切换,使i 进程投入执行。显然,这种抢占式的优先权调度算法能更好地满足紧迫作业的要求,故而常用于要求比较严格的实时系统中,以及对性能要求较高的批处理和分时系统中。  

【3-4】高响应比优先调度算法

1)在批处理系统中,短作业优先算法是一种比较好的算法,其主要的不足之处是长作业的运行得不到保证。如果我们能为每个作业引入前面所述的动态优先权,并使作业的优先级随着等待时间的增加而以速率a 提高,则长作业在等待一定的时间后,必然有机会分配到处理机。该优先权的变化规律可描述如下图:

  由于等待时间与服务时间之和就是系统对该作业的响应时间,故该优先权又相当于响应比RP,又可表示为:

 【3-5】时间片轮转法 

1)时间片轮转(Round Robin,RR)法的基本思路是让每个进程在就绪队列中的等待时间与享受服务的时间成比例。在时间片轮转法中,需要将CPU的处理时间分成固定大小的时间片,例如,几十毫秒至几百毫秒。如果一个进程在被调度选中之后用完了系统规定的时间片,但又未完成要求的任务,则它自行释放自己所占有的CPU而排到就绪队列的末尾,等待下一次调度。同时,进程调度程序又去调度当前就绪队列中的第一个进程。显然,轮转法只能用来调度分配一些可以抢占的资源。这些可以抢占的资源可以随时被剥夺,而且可以将它们再分配给别的进程。CPU是可抢占资源的一种。但打印机等资源是不可抢占的。由于作业调度是对除了CPU之外的所有系统硬件资源的分配,其中包含有不可抢占资源,所以作业调度不使用轮转法。 

2)在轮转法中,时间片长度的选取非常重要。首先,时间片长度的选择会直接影响到系统的开销和响应时间。如果时间片长度过短,则调度程序抢占处理机的次数增多。这将使进程上下文切换次数也大大增加,从而加重系统开销。反过来,如果时间片长度选择过长,例如,一个时间片能保证就绪队列中所需执行时间最长的进程能执行完毕,则轮转法变成了先来先服务法。时间片长度的选择是根据系统对响应时间的要求和就绪队列中所允许最大的进程数来确定的。

3)在轮转法中,加入到就绪队列的进程有3种情况:
      一种是分给它的时间片用完,但进程还未完成,回到就绪队列的末尾等待下次调度去继续执行。
      另一种情况是分给该进程的时间片并未用完,只是因为请求I/O或由于进程的互斥与同步关系而被阻塞。当阻塞解除之后再回到就绪队列。
      第三种情况就是新创建进程进入就绪队列。
      如果对这些进程区别对待,给予不同的优先级和时间片从直观上看,可以进一步改善系统服务质量和效率。例如,我们可把就绪队列按照进程到达就绪队列的类型和进程被阻塞时的阻塞原因分成不同的就绪队列,每个队列按FCFS原则排列,各队列之间的进程享有不同的优先级,但同一队列内优先级相同。这样,当一个进程在执行完它的时间片之后,或从睡眠中被唤醒以及被创建之后,将进入不同的就绪队列。   

 【3-6】多级反馈队列调度算法 

1) 应设置多个就绪队列,并为各个队列赋予不同的优先级。第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低。该算法赋予各个队列中进程执行时间片的大小也各不相同,在优先权愈高的队列中,为每个进程所规定的执行时间片就愈小。例如,第二个队列的时间片要比第一个队列的时间片长一倍,……,第i+1个队列的时间片要比第i个队列的时间片长一倍。

2)当一个新进程进入内存后,首先将它放入第一队列的末尾,按FCFS原则排队等待调度。当轮到该进程执行时,如它能在该时间片内完成,便可准备撤离系统;如果它在一个时间片结束时尚未完成,调度程序便将该进程转入第二队列的末尾,再同样地按FCFS原则等待调度执行;如果它在第二队列中运行一个时间片后仍未完成,再依次将它放入第三队列,……,如此下去,当一个长作业(进程)从第一队列依次降到第n队列后,在第n 队列便采取按时间片轮转的方式运行。

3)仅当第一队列空闲时,调度程序才调度第二队列中的进程运行;仅当第1~(i-1)队列均空时,才会调度第i队列中的进程运行。如果处理机正在第i队列中为某进程服务时,又有新进程进入优先权较高的队列(第1~(i-1)中的任何一个队列),则此时新进程将抢占正在运行进程的处理机,即由调度程序把正在运行的进程放回到第i队列的末尾,把处理机分配给新到的高优先权进程。  在多级反馈队列调度算法中,如果规定第一个队列的时间片略大于多数人机交互所需之处理时间时,便能够较好的满足各种类型用户的需要。

4、进程的并行与并发

1)并发 : 是指一个时间段内,有几个程序都在同一个CPU上运行,但任意一个时刻点上只有一个程序在处理机上运行。

2)并行 : 是指一个时间段内,有几个程序都在几个CPU上运行,任意一个时刻点上,有多个程序在同时运行,并且多道程序之间互不干扰。

3)区别:并发在微观上不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行,因为CPU计算速度快,从宏观上看,好像这些进程都在同一个时间点执行,并行是真正的细粒度上的同时进行:既同一时间点上同时运行着多个进程。

5、进程的三种状态 (参考

1)在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成(input)、申请缓冲区不能满足、等待信件(信号)等。

6、同步和异步

1)同步:在发出一个同步调用时,在没有得到结果之前,该调用就不返回。

 2)异步:在发出一个异步调用后,调用者不会立刻得到结果,该调用就返回了。

7、阻塞和非阻塞

1)阻塞调用是指调用结果返回之前,调用者会进入阻塞状态等待。只有在得到结果之后才会返回。

2)非阻塞调用是指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。 

8、同步/异步与阻塞/非阻塞

1)同步阻塞调用:得不到结果不返回,线程进入阻塞态等待。

2)同步非阻塞调用:得不到结果不返回,线程不阻塞一直在CPU运行。

3)异步阻塞调用:去到别的线程,让别的线程阻塞起来等待结果,自己不阻塞。

4)异步非阻塞调用:去到别的线程,别的线程一直在运行,直到得出结果。

9、进程的创建

1)但凡是硬件,都需要有操作系统去管理,只要有操作系统,就有进程的概念,就需要有创建进程的方式,一些操作系统只为一个应用程序设计,比如微波炉中的控制器,一旦启动微波炉,所有的进程都已经存在。而对于通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程。 

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

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

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

④一个批处理作业的初始化(只在大型机的批处理系统中应用)无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。 

【9-1】 创建子进程

1)在UNIX中该系统调用是:fork,fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)

2)在windows中该系统调用是:CreateProcess,CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。 

3)相同的是:进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。

4)不同的是:在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。  

10、进程的结束

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

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

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

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

11、在Python程序中的进程操作 

1)在Python中操作、管理进程需要借助multiprocessing包。该包主要包括了四部分:创建进程部分,进程同步部分,进程池部分,进程之间数据共享。

【1】multiprocessing.process模块

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

【例11-1】 创建子进程

from multiprocessing import Process
import time

def func(t):
    time.sleep(t)
    print("这里是子进程!")

# 在Windows操作系统中由于没有fork(linux操作系统中创建进程的机制),
# 在创建子进程的时候会自动 import 启动它的这个文件,而在 import 的时候又执行了整个文件。
# 因此如果将process()直接写在文件中就会无限递归创建子进程报错。
# 所以必须把创建子进程的部分使用if __name__ ==‘__main__’ 判断保护起来,import 的时候  ,
# 就不会递归运行了。
if __name__ == '__main__':
    # 创建子进程,target 要执行的任务;args 传递参数,元组形式,必须有逗号;
    # group 未使用,值始终为None;kwargs表示调用对象的字典;name为子进程的名称
    p = Process(target = func,args=(1,),name="子进程")
    # 启动进程,并调用该子进程中的p.run()
    p.start()
    print("这里是父进程!")

  返回结果 >>>

这里是父进程!
这里是子进程!

【例11-2】 进程属性

from multiprocessing import Process
import time

def func(t):
    print("子进程开始!")
    time.sleep(t)
    print("子进程结束!")

if __name__ == '__main__':
    p = Process(target = func,args=(1,),name="子进程")
    p.start()
    time.sleep(0.1)
    # 进程的ID
    print("子进程ID >>",p.pid)
    # 进程在运行时为None、如果为–N,表示被信号N结束,了解即可
    print("进程的整数代码退出 >>",p.exitcode)
    # 进程的名称
    print("子进程的名称 >>",p.name)
    # 进程的身份验证键,默认是由os.urandom()随机生成的32字符的字符串。
    # 这个键的用途是为涉及网络连接的底层进程间通信提供安全性,
    # 这类连接只有在具有相同的身份验证键时才能成功(了解即可)
    print("进程的身份验证键 >>",p.authkey)
    # 杀死进程,将任务提交给操作系统,会有点延迟
    p.terminate()
    time.sleep(0.1)
    # 判断p进程是否还存在
    print("进程是否存在:",p.is_alive())
    print("这里是父进程!")

  返回结果 >>>

子进程开始!
子进程ID >> 15444
进程的整数代码退出 >> None
子进程的名称 >> 子进程
进程的身份验证键 >> b'7\xa7S0\xed\xd1~\xf1\x05tG\xc2~O\x11\x87\xee\x14XJ\xe7NO\xc0v_\xc0bn\xa2\x98\x83'
进程是否存在: False
这里是父进程!

【例11-3】jion()方法

from multiprocessing import Process
import time

def func(t):
    time.sleep(t)
    print("这里是子进程!")

if __name__ == '__main__':
    p = Process(target = func,args=(1,),name="子进程")
    p.start()
    # join([timeout]) timeout 等待子进程运行的时间,默认为一直等
    p.join(2)
    print("这里是父进程!")

   返回结果 >>>

这里是子进程!
这里是父进程!

【例11-4】jion()方法

from multiprocessing import Process

def func(i):
    print("这里是子进程%s!"%(i))

if __name__ == '__main__':
    p_s=[]
    for i in range(5):
        p = Process(target = func,args=(i+1,),name="子进程")
        p.start()
        p_s.append(p)
    [i.join() for i in p_s]
    print("这里是父进程!")

   返回结果 >>>

这里是子进程2!
这里是子进程3!
这里是子进程4!
这里是子进程1!
这里是子进程5!
这里是父进程!

【例11-5】继承Process类

from multiprocessing import Process
from time import sleep

def func(i):
    sleep(i)
    print("这里是子进程!")

class MyProcess(Process):
    """继承Process类"""
    def __init__(self,target=None,args=()):
        super().__init__(target=target,args=args)

if __name__ == '__main__':
    p=MyProcess(target=func,args=(1,))
    p.start()
    print("这里是父进程!")

   返回结果 >>>

这里是父进程!
这里是子进程!

【例11-6】守护进程

from multiprocessing import Process
import time

def func():
    print("子进程开始执行")
    time.sleep(1)
    print("子进程结束执行")

if __name__ == '__main__':
    print("主进程开始执行")
    p = Process(target=func)
    # 将p设置为守护进程,此代码一定要在start之前设置。
    # 守护进程会在父进程代码执行结束后就终止
    # 守护进程内无法再开启子进程,否则抛出异常
    p.daemon = True
    p.start()
    time.sleep(0.5)
    print("主进程结束执行")

  返回结果 >>>

主进程开始执行
子进程开始执行
主进程结束执行

【例11-7】进程锁

from multiprocessing import Process
from multiprocessing import Lock
from multiprocessing import Value

def func(num,lock):
    # 开启锁
    lock.acquire()
    for i in  range(100):
        num.value+=1
    # 释放锁
    lock.release()

if __name__ == '__main__':
    # Value()使线程共享内存
    num=Value("i",0)
    lock=Lock()
    p_s=[]
    for i in range(3):
        p = Process(target=func,args=(num,lock,))
        p.start()
        p_s.append(p)
    [i.join() for i in p_s]
    # 不加锁可能导致数据混乱,使最后结果不为300
    print(num.value)

  返回结果 >>>

300

 【例11-8】信号量

from multiprocessing import Process
from multiprocessing import Semaphore
from time import sleep

def func(num,lock):
    # 开启锁,信号量内部计数器减1
    lock.acquire()
    print(">>>{0}号进来了".format(num))
    sleep(1)
    # 释放锁,信号量内部计数器加1
    lock.release()
    print("{0}号出去了>>>".format(num))

if __name__ == '__main__':
    # 信号量可以一把锁配置多把钥匙
    sema=Semaphore(3) 
    for i in range(10):
        p = Process(target=func,args=(i+1,sema,))
        p.start()

  返回结果 >>>

>>>2号进来了
>>>4号进来了
>>>1号进来了
2号出去了>>>
>>>3号进来了
4号出去了>>>
>>>5号进来了
1号出去了>>>
>>>7号进来了
3号出去了>>>
>>>8号进来了
5号出去了>>>
>>>9号进来了
7号出去了>>>
>>>6号进来了
8号出去了>>>
>>>10号进来了
9号出去了>>>
6号出去了>>>
10号出去了>>>

【例11-9】事件

from multiprocessing import Process
from multiprocessing import Event
from time import sleep

def traffic_lights(e):
    while 1:
        if e.is_set():   # 如果e.is_set()为True,代表e.wait()不阻塞
            print('\033[31m红灯亮!\033[0m')
            print('\033[31m禁止通行!\033[0m')
            e.clear()    # 将e.is_set()改成False
            sleep(3)
        else:
            print('\033[32m绿灯亮!\033[0m')
            e.set()      # 将e.is_set()改成True
            sleep(3)
def car(e):
    for i in range(12):
        e.wait()         # 读取是否阻塞
        print('第%s辆车过'%(i+1))
        sleep(0.5)


if __name__ == '__main__':
    # 事件机制,主要用于主进程控制其他进程的执行
    e = Event()
    # 开启红绿灯进程
    triff_light = Process(target=traffic_lights,args=(e,))
    triff_light.start()
    # 开启汽车通行进程
    cars = Process(target=car,args=(e,))
    cars.start()
    cars.join()
    triff_light.terminate()

  返回结果 >>>

绿灯亮!
第1辆车过
第2辆车过
第3辆车过
第4辆车过
第5辆车过
第6辆车过
红灯亮!
禁止通行!
绿灯亮!
第7辆车过
第8辆车过
第9辆车过
第10辆车过
第11辆车过
第12辆车过
红灯亮!
禁止通行!

作者水平有限,若有问题请留言,或者微信一起讨论、学习! 

  • 9
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值