• day28-2 并发编程I


    并发编程

    串行、并发和并行

    串行:程序自上而下的顺序执行,必须把当前任务执行完毕才能执行下一个任务,无论当前任务需要多长时间

    并发:多个任务同时执行。但是本质上是不同进程间的切换执行,由于切换速度非常快所以感觉是同时运行。学习并发的目的就是编写可以同时执行多个任务的程序,来提高效率

    并行:多个任务真正的同时运行,必须具备多核CPU,有几个核心就能并行几个任务,当任务数量超过核心数还是并发执行

    以上三个概念都是用于描述处理任务的方式

    阻塞和非阻塞

    阻塞:指的是程序遇到IO操作,如input(),无法继续执行代码时的一种状态

    非阻塞:指的是程序没有遇到IO操作的一种状态。又分为运行和就绪两种状态

    阻塞和非阻塞是用来描述程序的状态,可以用一些手段将阻塞的操作变成非阻塞的操作

    进程与多进程

    进程:指的是正在运行的程序,是操作系统在调度和进行资源分配的基本单位。当把一个程序从硬盘读入到内存时,进程就产生了

    多进程:指的是同一时间有多个程序被装入内存并执行,实现原理就是操作系统调度进程的原理

    进程和程序

    程序:是一堆代码放在一个文件中,文件通常后缀为exe。在linux系统中没有后缀

    进程:是将代码从硬盘读取到内存然后执行产生的。本质上就是一个程序在一个数据集上的一次动态执行过程。一般由程序、数据集(程序运行时产生的数据)、进程控制块三部分组成。

    注意:一个程序可以产生多个进程,每一个进程都具备一个PID(进程编号),且是唯一的

    进程的创建和销毁

    创建

    • 用户的交互式请求,如鼠标双击
    • 由一个正在运行的程序调用了开启进程的接口,如subprocess
    • 一个批处理作业的开始
    • 系统初始化

    销毁

    • 任务完成——>自愿退出
    • 强制结束,在windows系统中用taskkill命令,在linux系统中用kill命令——>非自愿退出
    • 程序遇到了异常——>自愿退出
    • 严重错误,比如访问了不该访问的内存

    进程的层次结构

    例如:在QQ中开启了浏览器,那么QQ是父进程,浏览器是子进程

    在linux系统中,进程具备父子关系,是一个树状结构,可以相互查找到对方

    在windows系统中,进程没有层级关系,父进程可以将子进程的句柄转让

    PID和PPID

    PID:当前进程的编号,可用os.getpid()获得

    PID:是父进程的编号,可用os.getppid()获得

    注意:我们在运行py文件时,其实运行的是python解释器

    python如何使用多进程

    创建子进程的方式一

    导入multiprocessing中的Process类,实例化这个类,指定要执行的任务target

    import os
    from multiprocessing import Process
    
    
    def task(name):
    	# 子进程
        print(f'Son {name} process:', os.getpid())
        print(f'{name} parent process:', os.getppid())
    
        
    # 开启进程的代码必须放在__main__判断下面
    if __name__ == '__main__':
        # 实例化一个进程对象,并用函数来指定子进程要做的事情
        p = Process(target=task, args=("nick",)) # args是传入字子进程中的参数
        p.start()  # 开启子进程
    
        print('Parent process:', os.getpid())
        
    -----------------------------------------------------
    Parent process: 32692
    Son nick process: 17508
    nick parent process: 32692
    

    创建子进程的方式二

    导入multiprocessing中的Process类,继承这个类,覆盖run方法,将要执行的任务放入run中,开启进程时会自动执行该函数

    import os
    from multiprocessing import Process
    
    
    class MyProcess(Process):
        
        # 如果需要对进程对象进行高度自定义,就可以继承它
        # def __init__(self,url,size,name):
        #     super().__init__()
        #     self.url = url
        #     self.size = size
        #     self.name = name
            
        def run(self):
            print('Son process:', os.getpid())
            print('Its parent process:', os.getppid())
            
    
    if __name__ == '__main__':
        p = MyProcess()
        p.start()  # 启动进程时会自动执行run函数
    
        print('Parent process:', os.getpid())
    
    -----------------------------------------------------
    Parent process: 17488
    Son process: 34016
    Its parent process: 17488
    

    注意:linux与windows系统开启进程的方式不同,linux会将父进程的内存数据完整的复制一份给子进程,windows会导入父进程的代码从头执行一遍来获取需要处理的任务,所以在编写代码时windows系统一定要将开启进程的代码放在main判断中,linux可以不放

    进程之间内存相互隔离

    import time
    from multiprocessing import Process
    
    a = 257
    
    
    def task():
        global a
        print('Son process:', id(a))
    
    
    if __name__ == '__main__':
        p = Process(target=task)
        # 进程之间的内存相互隔离,在子进程中修改a的大小不会影响父进程中的a
        print('Parent process old:', id(a))
        p.start()
    
        time.sleep(10)
    
        print('Parent process new:', id(a))
        
    -----------------------------------------------------
    Parent process old: 2117832912592
    Son process: 2075741633392
    Parent process new: 2117832912592
    

    join函数:让主进程等待子进程执行完毕再继续执行

    # 一个子进程
    from multiprocessing import Process
    import time
    
    def task1(name):
        for i in range(100):
            print("%s run" % name)
    
    if __name__ == '__main__': # args 是给子进程传递的参数 必须是元组
        p1 = Process(target=task1,args=("p1",))
        p1.start()  # 向操作系统发送指令
        p1.join()   # 让主进程 等待子进程执行完毕再继续执行
        p1.join()
    
        print("over")
    -----------------------------------------------------   
    # 多个子进程
    from multiprocessing import Process
    import time
    def task1(name):
        for i in range(10000):
            print("%s run" % name)
    
    def task2(name):
        for i in range(100):
            print("%s run" % name)
    
    if __name__ == '__main__': # args 是给子进程传递的参数,必须是元组
        p1 = Process(target=task1,args=("p1",))
        p1.start()  # 向操作系统发送指令
    
        p2 = Process(target=task2,args=("p2",))
        p2.start()  # 向操作系统发送指令
    
        p2.join()  # 让主进程等待子进程执行完毕再继续执行
        p1.join()
    
        # 需要达到的效果是:必须保证两个子进程是并发执行的,并且 over一定是在所有任务执行完毕后执行
        print("over")
    -----------------------------------------------------    
    # join的使用
    from multiprocessing import Process
    
    
    def task1(name):
        for i in range(10):
            print("%s run" % name)
    
    
    if __name__ == '__main__':  # args 是给子进程传递的参数 必须是元组
    
        ps = []
        for i in range(10):
            p = Process(target=task1, args=(i,))
            p.start()
            ps.append(p)
    
        # 挨个join以下
        for i in ps:
            i.join()
    
        print("over")
    

    进程对象的常用属性

    属性 描述
    p.name 进程名字,可以在实例化对象是为进程设置name参数
    p.daemon 守护进程
    p.exitcode 进程的退出码,在进程的exit()函数中传入的值
    p.is_alive() 进程是否存活
    p.pid() 进程id(子进程)
    p.terminate() 终止进程,与start相同不会立即终止,操作系统需要时间
    p.start() 启动进程

    僵尸进程与孤儿进程

    孤儿进程:当父进程已经结束,而子进程还在运行,子进程就称为孤儿进程。有其存在的必要性,没有不良影响

    僵尸进程:当一个进程已将结束了,但是它仍然还有一些数据存在,此时称之为僵尸进程

    在linux中有这么一个机制,父进程无论什么时候都可以获取到子进程的一些数据。子进程在任务执行完毕后,确实结束了但是仍然保留一些数据,目的是为了让父进程能够获取这些信息。

    在linux中可以调用waitpid来彻底清除子进程的残留信息

    在python中已经封装了处理僵尸进程的操作,所以无需关心

  • 相关阅读:
    周报说明8-4
    周报7-28
    日志-maven-c监控 周报7-21
    证明创建runnable实例和普通类时间一样长, 其实吧
    UE4开始之路
    js简单框架设计
    json中key为数字会自动排序问题
    u3动画
    工厂模式
    单例模式
  • 原文地址:https://www.cnblogs.com/863652104kai/p/11123341.html
Copyright © 2020-2023  润新知