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中已经封装了处理僵尸进程的操作,所以无需关心

posted @ 2019-07-02 21:41  Never&say&die  阅读(147)  评论(0编辑  收藏  举报