网络基础之 并发编程之进程,多路复用,multiprocess模块

并发
1. 背景知识
2. 什么是进程
3. 进程调度
4. 并发与并行
5 同步\异步\阻塞\非阻塞(重点)
6.multiprocess模块
7.僵尸进程与孤儿进程

1.背景知识

一操作系统的作用:
1:隐藏丑陋复杂的硬件接口,提供良好的抽象接口
2:管理、调度进程,并且将多个进程对硬件的竞争变得有序

二 多道技术:
1.产生背景:针对单核,实现并发
ps:现在的主机一般是多核,那么每个核都会利用多道技术有4个cpu,运行于cpu1的某个程序遇到io阻塞,会等到io结束再重新调度,
会被调度到4个cpu中的任意一个,具体由操作系统调度算法决定。

2.空间上的复用:如内存中同时有多道程序

3.时间上的复用:复用一个cpu的时间片
强调:遇到io切,占用cpu时间过长也切,核心在于切之前将进程的状态保存下来,这样才能保证下次切换回来时,能基于上次切走的位置继续运行


2. 什么是进程

狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。

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

第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。[3]
进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。

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

多个不同的进程可以包含相同的程序:一个程序在不同的数据集里就构成不同的进程,能得到不同的结果;但是执行过程中,程序不能发生改变。

程序和进程的区别

程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。
而进程是程序在处理机上的一次执行过程,它是一个动态的概念。
程序可以作为一种软件资料长期存在,而进程是有一定生命期的。
程序是永久的,进程是暂时的。

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


4. 并发与并行
无论是并行还是并发,在用户看来都是'同时'运行的,不管是进程还是线程,都只是一个任务而已,真是干活的是cpu,cpu来做这些任务,而一个cpu同一时刻只能执行一个任务
并发:是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)
并行:同时运行,只有具备多个cpu才能实现并行

多道技术概念回顾:内存中同时存入多道(多个)程序,cpu从一个进程快速切换到另外一个,使每个进程各自运行几十或几百毫秒,
这样,虽然在某一个瞬间,一个cpu只能执行一个任务,但在1秒内,cpu却可以运行多个进程,这就给人产生了并行的错觉,即伪并行,以此来区分多处理器操作系统的真正硬件并行(多个cpu共享同一个物理内存)


5.同步\异步\阻塞\非阻塞(重点)
1.进程状态介绍
(1)就绪(Ready)状态当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
(2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
(3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
    事件请求:input、sleep、文件输入输出、recv、accept等
    事件发生:sleep、input等完成了
    时间片到了之后有回到就绪状态,这三个状态不断的在转换

2.同步异步

1.所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
2.所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了。
至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列。

3.阻塞与非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。
也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的


4.同步/异步与阻塞和非阻塞

同步阻塞形式:效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。

异步阻塞形式:如果在排队取餐的人采用的是异步的方式去等待消息被触发(通知),也就是领了一张小纸条,假如在这段时间里他不能做其它的事情,就在那坐着等着,不能玩游戏等,那么很显然,这个人被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。

同步非阻塞形式:实际上是效率低下的。想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,
这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。

异步非阻塞形式:效率更高,因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换。

tips:很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞。


6.multiprocess模块
1.process模块介绍
process模块是一个创建进程的模块,借助这个模块,就可以完成进程的创建。

主进程创建的子进程是异步执行的,那么我们就验证一下,并且看一下子进程和主进程(也就是父进程)的ID号(讲一下pid和ppid,使用pycharm举例),来看看是否是父子关系。
from multiprocessing import Process
import time,os
def func1():
    time.sleep(2)
    print("我是func1")
    print("主进程>>",os.getpid())
def func2():
    time.sleep(2)
    print("我是func2")
    print("子进程>>",os.getpid())
    print("子进程的父进程>>", os.getppid())

if __name__ == "__main__":
    p = Process(target=func2)
    p.start()
    func1()
    print("主进程的父进程>>",os.getppid())

Process类中参数的介绍:
参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组,args=(1,2,'egon',)
4 kwargs表示调用对象的字典,kwargs={'name':'egon','age':18}
5 name为子进程的名称

给要执行的函数传参数:
from multiprocessing import Process
import time
def func(m,n):
    print(m)
    time.sleep(1)
    print(n)
if __name__ == '__main__':
    p1 = Process(target=func,args=(10,20))
    p1.start()

 

Process类中各方法的介绍:
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开启的进程

 Process类中自带封装的各属性的介绍
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字符的字符串。这个键的用途是为涉及网络连接的底层进程间通信提供安全性,这类连接只有在具有相同的身份验证键时才能成功(了解即可)

2.Process类的使用

进程的创建第二种方法(继承)
from multiprocessing import Process
import time
class myprocess(Process):
    def __init__(self,n):
        super().__init__()
        self.n = n
    def run(self):       #想要定义功能,必须采用run的名字,因为target会调用名称为run的函数
        time.sleep(2)
        print("我是run")
if __name__ == '__main__':
    p1 = myprocess(10)
    p1.start()

进程之间的数据是隔离的:
验证实例:
from multiprocessing import Process
import time,os
num = 100
def func():
    global num
    num -= 1
    print("子程序pid:%s,子程序的父程序%s,num=%s"%(os.getpid(),os.getppid(),num))
lis = []
if __name__ == '__main__':
    for i in range(10):
        p = Process(target=func)
        p.start()
        lis.append(p)
    for el in lis:
        el.join()
    print("主程序pid:%s,num=%s"%(os.getpid(),num))

进程对象的其他方法一:terminate,is_alive

from multiprocessing import Process
import time
import random

class Piao(Process):
    def __init__(self,name):

        super().__init__()
        self.name = name
#
    def run(self):
        print('%s is 玩泥巴' %self.name)
        # s = input('???') #别忘了再pycharm下子进程中不能input输入,会报错EOFError: EOF when reading a line,因为子进程中没有像我们主进程这样的在pycharm下的控制台可以输入东西的地方
        time.sleep(2)
        print('%s is 玩泥巴结束' %self.name)

if __name__ == '__main__':
    p1 = Piao("尼古拉斯")
    p1.start()
    time.sleep()
    p1.terminate()#关闭进程,不会立即关闭,有个等着操作系统去关闭这个进程的时间,所以is_alive立刻查看的结果可能还是存活,但是稍微等一会,就被关掉了
    print(p1.is_alive()) #结果为True
    print('等会。。。。')
    time.sleep(1)
    print(p1.is_alive()) #结果为False

 


7.僵尸进程与孤儿进程(简单了解 一下就可以啦)
  僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵死进程。详解如下
我们知道在unix/linux中,正常情况下子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束,如果子进程一结束就立刻回收其全部资源,那么在父进程内将无法获取子进程的状态信息。

孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程就好像是一个民政局,专门负责处理孤儿进程的善后工作
posted @ 2018-11-27 19:18  Mixtea  阅读(835)  评论(0编辑  收藏  举报