python编程操作系统篇知识点详细梳理(上)

进程的概念:(Process)

  • 进程就是正在运行的程序,它是操作系统中资源分配的最小单位。
    • 资源分配:操作系统分配的CPU时间片、内存、磁盘空间端口等等资源。
    • 进程号(process identification)是操作系统分配给进程的唯一标识号,用户每打开一个进程操作系统都会为其创建PID。
      • UID:用户id; PID:进程id; PPID:父进程id。
    • 在存储空间中未被执行的叫程序,被执行的叫进程(进行中的程序)。
    • 同一个程序执行两次之后是两个进程。
    • 进程和进程之间的关系:进程之间数据隔离,但可通过socket通信。

并行和并发

  • 并发:单个CPU通过操作系统调度以极快地速度轮流执行多个进程,给用户的感觉是多个进程正在同时执行。并发在逻辑上是同时执行,实质上是轮流执行。
  • 并行:多个CPU在同一时间分别执行不同的进程。并行是真正的同时执行。

CPU调度策略

  • 先来先服务:先来的先执行。

  • 短作业优先:短作业优先执行。

  • 时间片轮转:每个作业执行一个时间片后轮转其它作业执行。

  • 多级反馈队列:

    1. 进程首次启动时进入优先级最高的Q1队列等待;
    2. 优先执行Q1队列中的进程;若高优先级Q1队列中无待执行的进程,那么会执行Q2队列中的进程;若Q1、Q2队列中都无待执行的进程,那么会执行Q3队列中的进程;以此类推,直至末尾队列的进程。
    3. 对同级队列中的进程按先来先服务的策略分配时间片。如Q1队列的时间片为N,若Q1中的进程用完时间片N后还未完成时会被调整到Q2队列;若用完Q2队列的时间片后还未完成时会被调整到Q3队列;以此类推,直至被调整到末尾队列。
    4. 在末尾队列QN中的各个进程,按照时间片轮转执行。
    5. 如果低优先级队列中的进程在运行时有新进程需要运行,那么它会被中断运行并被放入当前队列的队尾,然后让新进程优先运行。另外被中断运行的进程再度运行时它只能得到上次未用完的时间片。
    6. 优先级越高的队列时间片越短,优先级越低的队列时间片越长。如三级反馈队列Q1、Q2、Q3它们的时间片分别为2、4、8。

进程三状态

  1. 就绪(Ready)状态:等待被CPU执行的状态。
  2. 执行(Running)状态:正在被CPU正在执行的状态。
  3. 阻塞(Blocked)状态:等待某个事件发生(如等待用户输入)而无法执行的状态。
    在这里插入图片描述
    代码示意图:
    在这里插入图片描述

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

同步与异步、阻塞与非阻塞发生在多任务场景中:

  • 同步:是指A进程调用B进程后,A进程要等B进程完成后才能继续运行,这是单进程运行状态。上面的代码示意图中就是同步。
  • 异步:是指A进程调用B进程后,A进程不等B进程完成,它和B进程可以同时执行,这是多进程(或线程)运行状态。
  • 阻塞:是指进程调用了某些I/O操作后进入挂起状态,要等I/O返回的结果才继续执行。
  • 非阻塞:是指进程调用了某些I/O操作后不进入挂起状态,不用等待I/O返回的结果就继续执行。

四种状态:

同步阻塞:单进程运行有阻塞事件时,等待阻塞事件完成后才能继续运行。

异步阻塞:某进程运行时有多条子进程(或线程)同步运行,它的某条子进程有阻塞事件进入阻塞状态,而其它子进程仍然正常运行。

同步非阻塞:没有阻塞事件,单进程正常执行的状态。

异步非阻塞:多进程运行且无阻塞事件。

多进程

  • 处理多进程的模块:

    注意:导入的是Process类,首字母必须大写,另外小写的是process文件。

    from multiprocessing import Process
    
  • 函数式编程方式:

    案例:

    from multiprocessing import Process
    import os
    
    
    def func(n):
        for i in range(n):
            print("func", os.getpid(), os.getppid())
    
    
    if __name__ == '__main__':
        print("main", os.getpid(), os.getppid())
        p = Process(target=func, args=(1,))
        p.start()
    
        
    out:
    main 6928 6621
    func 6929 6928
    

    代码说明:

    • os.getpid:获取进程号。
    • os.getppid:获取父进程号。
    • p = Process(target=func, args=(1,)):创建Process类的实例,目标是func函数,args=(1,)是给函数传参数(注意agrs=后面必须是元祖,1后面的逗号不能少,少了就不是元祖,会报错),以此创建子进程。
  • 面向对象式编程方式:

    案例:

    from multiprocessing import Process
    from time import sleep
    from os import getpid, getppid
    
    
    class MyProcess(Process):
        def __init__(self, n):
            self.n = n
            super().__init__()
    
        def run(self):
            sleep(0.1)
            print(f"第{self.n}次打印,子进程id:{getpid()},父进程id:{getppid()}")
    
    
    if __name__ == "__main__":
        for i in range(5):
            MyProcess(i).start()
        print("*" * 20)
    
        
    out:
    ********************0次打印,子进程id13029,父进程id130282次打印,子进程id13031,父进程id130281次打印,子进程id13030,父进程id130283次打印,子进程id13032,父进程id130284次打印,子进程id13033,父进程id13028
    

    代码说明:

    • 面向对象编程必须自定义一个类并继承Process类。
    • 必须使用__init__传参,在传参完成后必须调用父类的__init__方法才能正确完成初始化。
    • 创建子进程的方法就是初始化自定义类的方法。
  • Process类常用的属性和方法:

    先看Process类源码:

    class Process(process.BaseProcess):
        _start_method = None
        @staticmethod
        def _Popen(process_obj):
            return _default_context.get_context().Process._Popen(process_obj)
    

    Process类本身内容很少,但它继承了process.BaseProcess类,再来看process.BaseProcess类:

    class BaseProcess:
        name: str
        daemon: bool
        authkey: bytes
        def __init__(
            self,
            group: None = ...,
            target: Optional[Callable[..., Any]] = ...,
            name: Optional[str] = ...,
            args: Tuple[Any, ...] = ...,
            kwargs: Mapping[str, Any] = ...,
            *,
            daemon: Optional[bool] = ...,
        ) -> None: ...
        def run(self) -> None: ...
        def start(self) -> None: ...
        def terminate(self) -> None: ...
        if sys.version_info >= (3, 7):
            def kill(self) -> None: ...
            def close(self) -> None: ...
        def join(self, timeout: Optional[float] = ...) -> None: ...
        def is_alive(self) -> bool: ...
        @property
        def exitcode(self) -> Optional[int]: ...
        @property
        def ident(self) -> Optional[int]: ...
        @property
        def pid(self) -> Optional[int]: ...
        @property
        def sentinel(self) -> int: ...
    

    代码说明:

    • run方法:即多进程需要执行的代码主体。
    • start方法:即多进程启动运行。
    • self.terminate方法:强制终止子进程。请注意这个方法是调用操作系统来关闭子进程,通常需要零点零零几秒的片刻时间后该子进程才会被终止。这是异步非阻塞的方法,即该方法通知操作系统后会立即继续执行后续代码,它不等待操作系统返回结果,后续代码运行的时候操作系统杀进程的代码也在同步运行的。
    • is_alive方法:查看子进程是否活着。用self.terminate方法结束子进程后立即查看可能还是True即活着的状态,要等待操作系统执行完杀进程的操作后才会返回False即死了的状态。
    • self.pid和self.ident属性:它们是被property装饰的方法,返回当前进程的id,这2个属性内容完全一致。
    • self.exitcode属性:返回子进程结束时的状态码。
  • 不同操作系统平台下的差异:

    • windows平台下创建子进程是通过加载py文件来获取所需的数据和代码,假如不写“ if __name__ == ‘__main__’ ”会造成递归加载py文件导致加载失败!
    • linux和mac平台下创建子进程是通过拷贝父进程内存空间来获取的所需的数据和代码,所以在linux和mac平台下不写“ if __name__ == ‘__main__’ ”也可以正常执行,不会导致加载失败!
    • 在linux平台下创建和运行子进程的效率比window平台下高得多。
  • 不同子进程之间内存隔离,不能直接共享数据。但是可以通过socket通信。

  • 开启多个子进程的示范:

    from multiprocessing import Process
    import time
    
    
    def func(name):
        time.sleep(0.5)
        print('子进程:', name)
    
    
    if __name__ == '__main__':
        print("父进程:")
        name_list = ['张三', '李四', '王五']
        for i in name_list:
            p = Process(target=func, args=(i,))
            p.start()
    
    out:
    父进程:
    子进程: 张三
    子进程: 王五
    子进程: 李四
    

    代码说明:

    • 在该案例中time.sleep(0.5)是阻塞事件,多条子进程各自执行,遇到阻塞时各自等待,相互不干扰。这就是异步阻塞
    • 可以使用循环的方式创建多条子进程。
  • join阻塞主进程,主进程等待被join的子进程运行结束后才继续运行:

    • 模拟多进程下载文件的错误代码:

      from multiprocessing import Process
      import time
      import random
      
      
      def func(name):
          time.sleep(random.random())
          print('子进程:', name)
      
      
      if __name__ == '__main__':
          name_list = ['下载完第一部分', '下载完第二部分', '下载完第三部分', '下载完第四部分', '下载完第五部分']
          for i in name_list:
              p = Process(target=func, args=(i,))
              p.start()
          print("文件五个部分下载完成,合并完毕!")
      
      
      out:
      文件五个部分下载完成,合并完毕!
      子进程: 下载完第四部分
      子进程: 下载完第一部分
      子进程: 下载完第五部分
      子进程: 下载完第二部分
      子进程: 下载完第三部分
      

      代码说明:

      • 上述代码模拟多进程下载文件。假设不阻塞主进程,那么这个程序无法保证正确执行。
      • 要保证上述代码正常运行就必须阻塞主进程,等待所有子进程下载完毕后主进程才能继续执行后续的合并和校验文件以及告知用户下载完成的工作。
    • 模拟多进程下载文件的正确代码:

      from multiprocessing import Process
      import time
      import random
      
      
      def func(name):
          time.sleep(random.random())
          print('子进程:', name)
      
      
      if __name__ == '__main__':
          name_list = ['下载完第一部分', '下载完第二部分', '下载完第三部分', '下载完第四部分', '下载完第五部分']
          process_list = []
          for i in name_list:
              p = Process(target=func, args=(i,))
              p.start()
              process_list.append(p)
          for i in process_list:
              i.join()
          print("文件五个部分下载完成,合并完毕!")
      
          
      out:
      子进程: 下载完第二部分
      子进程: 下载完第五部分
      子进程: 下载完第一部分
      子进程: 下载完第三部分
      子进程: 下载完第四部分
      文件五个部分下载完成,合并完毕!
      

      代码说明:

      • 在上述代码中,每次创建并开启子进程后,会将子进程的对象内存地址存入process_list列表中。
      • 所有子进程创建完毕后,遍历process_list列表,将所有子进程对象设为阻塞事件,设置方法是p.join().
  • 守护进程:

    • 给子进程设置守护进程属性为True,该子进程会随着主进程代码执行完毕而结束。

      p.daemon = True
      
    • 设置守护进程属性语句必须在子进程启动语句前面。

      p.daemon = True
      p.start()
      
    • 守护进程内无法再开启子进程。

    • 案例,看案例请思考一个问题,son1的打印语句会执行几次?

      import time
      from multiprocessing import Process
      
      def son1():
          while True:
              print('->1号子进程')
              time.sleep(1)
      
      def son2():
          for i in range(5):
              print('->2号子进程')
              time.sleep(1)
      
      if __name__=="__main__":
          p1 = Process(target=son1)
          p1.daemon = True
          p1.start()
          p2 = Process(target=son2)
          p2.start()
          print("->主进程")
          time.sleep(3)
      
          
      out:
      ->主进程
      ->1号子进程
      ->2号子进程
      ->1号子进程
      ->2号子进程
      ->1号子进程
      ->2号子进程
      ->2号子进程
      ->2号子进程
      

      代码说明:

      • 在上述案例中,son1函数即1号子进程每隔1秒打印"->1号子进程"(无限循环);
      • son2函数即2号子进程每隔1秒打印"->2号子进程"(循环5次);
      • 在主进程代码中对son1设守护进程属性为True,然后启动son1子进程;
      • 对son2未设守护进程属性(默认为False),然后启动son2子进程;
      • 主进程sleep3秒,显示结果是son1打印了3次。即主进程的代码3秒执行完毕后son1子进程会被强制结束!
      • son2子进程未设daemon属性,它正常打印了5次,它不会随着主进程的代码结束而结束。
      • 结论:子进程daemon属性为True的是守护进程,在主进程代码结束时它会被强制结束;子进程daemon属性为False的是非守护进程,在主进程代码结束时它仍然会正常运行直至运行完毕;主进程代码结束后守护进程会立即结束,之后python解释器还会做一些回收资源的工作,最后主进程才真正结束。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值