多任务编程

多任务概述

即操作系统中可以同时运行多个任务。比如我们可以同时挂着qq,听音乐,同时上网浏览网页。这是我们看得到的任务,在系统中还有很多系统任务在执行,现在的操作系统基本都是多任务操作系统,具备运行多任务的能力

13.png

cpu轮询机制,cpu都在多个任务之间快速的切换执行,切换速度在微秒级别,其实cpu同时只执行一个任务,但是因为切换太快了,从应用层看好像所有任务同时在执行

16.gif

  • 多核CPU:现在的计算机一般都是多核CPU,比如四核,八核,我们可以理解为由多个单核CPU的集合。这时候在执行任务时就有了选择,可以将多个任务分配给某一个cpu核心,也可以将多个任务分配给多个cpu核心,操作系统会自动根据任务的复杂程度选择最优的分配方案。
  • 并发 : 多个任务如果被分配给了一个cpu内核,那么这多个任务之间就是并发关系,并发关系的多个任务之间并不是真正的"同时"。
  • 并行 : 多个任务如果被分配给了不同的cpu内核,那么这多个任务之间执行时就是并行关系,并行关系的多个任务时真正的“同时”执行

什么是多任务编程

  • 多任务编程即一个程序中编写多个任务,在程序运行时让这多个任务一起运行,而不是一个一个的顺次执行.比如微信视频聊天,这时候在微信运行过程中既用到了视频任务也用到了音频任务,甚至同时还能发消息。这就是典型的多任务。而实际的开发过程中这样的情况比比皆是

12.jpg

多任务意义

  1. 提高了任务之间的配合,可以根据运行情况进行任务创建。
  2. 充分利用计算机资源,提高了任务的执行效率。
  • 在任务中无阻塞时只有并行状态才能提高效率

17.jpg

  • 在任务中有阻塞时并行并发都能提高效率

18.jpg

进程(Process)

进程概述

  • 程序在计算机中的一次执行过程
  • 程序是一个可执行的文件,是静态的占有磁盘
  • 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期

进程状态

  • 三态
  1. 就绪态:进程具备执行条件,等待系统调度分配cpu资源
  2. 运行态:进程占有cpu正在运行
  3. 等待态:进程阻塞等待,此时会让出cpu

4_3.png

五态 (在三态基础上增加新建和终止)

  1. 新建:创建一个进程,获取资源的过程
  2. 终止:进程结束,释放资源的过程

4_5.png

进程命令

#查看进程信息
ps -aux

#进程树形结构
#父子进程:在Linux操作系统中,进程形成树形关系,任务上一级进程是下一级的父进程,下一级进程是上一级的子进程
pstree
  • USER : 进程的创建者
  • PID : 操作系统分配给进程的编号,大于0的整数,系统中每个进程的PID都不重复。PID也是重要的区分进程的标志。
  • %CPU,%MEM : 占有的CPU和内存
  • STAT : 进程状态信息,S I 表示阻塞状态 ,R 表示就绪状态或者运行状态
  • START : 进程启动时间
  • COMMAND : 通过什么程序启动的进程

20.png

多进程编程

使用模块 multiprocessing

  • 创建流程
  1. 将需要新进程执行的事件封装为函数
  2. 通过模块的Process类创建进程对象,关联函数
  3. 通过进程对象调用start启动进程
  • 主要类和函数使用
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数 
       args 元组,用于给target函数位置传参
       kwargs 字典,给target函数键值传参
       daemon  bool值,让子进程随父进程退出

p.start()
功能 : 启动进程
#注意 : 启动进程此时target绑定函数开始执行,该函数作为新进程执行内容,此时进程真正被创建

p.join([timeout])
功能:阻塞等待子进程退出
参数:最长等待时间
进程创建示例:
"""
进程创建示例 01
"""
import multiprocessing as mp
from time import sleep

a = 1   # 全局变量

# 进程目标函数
def fun():
    print("开始运行一个进程")
    sleep(4)  # 模拟事件执行事件
    global a
    print("a =",a)  # Yes
    a = 10000
    print("进程执行结束")


# 实例化进程对象
process = mp.Process(target=fun)

# 启动进程  进程产生 执行fun
process.start()

print("我也做点事情")
sleep(3)
print("我也把事情做完了...")

process.join() # 阻塞等待子进程结束
print("a:",a) # 1  10000
"""
进程创建示例02 : 含有参数的进程函数
"""
from multiprocessing import Process
from time import sleep

# 含有参数的进程函数
def worker(sec,name):
    for i in range(3):
        sleep(sec)
        print("I'm %s"%name)
        print("I'm working....")

# 元组位置传参
# p = Process(target=worker,args=(2,"Tom"))

# 关键字传参
p = Process(target=worker,
            args = (2,),
            kwargs={"name":"Tom"},
            daemon=True) # 子进程伴随父进程结束
p.start()
sleep(3)

进程执行现象理解

  • 新的进程是原有进程的子进程,子进程复制父进程全部内存空间代码段,一个进程可以创建多个子进程。
  • 子进程只执行指定的函数,其余内容均是父进程执行内容,但是子进程也拥有其他父进程资源。
  • 各个进程在执行上互不影响,也没有先后顺序关系。
  • 进程创建后,各个进程空间独立,相互没有影响。
  • multiprocessing创建的子进程中无法使用标准输入(即无法使用input)

进程处理细节

进程相关函数

os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID 

os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID

sys.exit(info)
功能:退出进程
参数:字符串 表示退出时打印内容
"""
创建多个子进程
"""
from multiprocessing import Process
from time import sleep
import sys, os


def th1():
    sleep(3)
    print("吃饭")
    print(os.getppid(), "--", os.getpid())


def th2():
    # sys.exit("不能睡觉了") # 进程结束
    sleep(1)
    print("睡觉")
    print(os.getppid(), "--", os.getpid())


def th3():
    sleep(2)
    print("打豆豆")
    print(os.getppid(), "--", os.getpid())


# 循环创建子进程
jobs = [] # 存放每个进程对象
for th in [th1, th2, th3]:
    p = Process(target=th)
    jobs.append(p) # 存入jobs
    p.start()

#  确保三件事都结束
for i in jobs:
    i.join()
print("三件事完成")

孤儿进程和僵尸进程

  • 孤儿进程: 父进程先于子进程退出时,子进程会成为孤儿进程,孤儿进程会被系统自动收养,成为孤儿进程新的父进程,并在孤儿进程退出时释放其资源。
  • 僵尸进程: 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会成为僵尸进程。
  • 特点:僵尸进程虽然结束,但是会存留部分进程资源在内存中,大量的僵尸进程会浪费系统资源。Python模块当中自动建立了僵尸处理机制,每次创建新进程都进行检查,将之前产生的僵尸处理掉,而且父进程退出前,僵尸也会被自动处理。

创建进程类

进程的基本创建方法将子进程执行的内容封装为函数。如果我们更热衷于面向对象的编程思想,也可以使用类来封装进程内容

  • 创建步骤
  1. 继承Process
  2. 重写__init__方法添加自己的属性,使用super()加载父类属性
  3. 重写run()方法
  • 使用方法
  1. 实例化对象
  2. 调用start自动执行run方法
"""
自定义进程类
"""
from multiprocessing import Process
from time import sleep


class MyProcess(Process):
    def __init__(self, value):
        self.value = value
        super().__init__()  # 调用父类的init

    # 重写run 作为进程的执行内容
    def run(self):
        for i in range(self.value):
            sleep(2)
            print("自定义进程类。。。。")

p = MyProcess(3)
p.start() # 将 run方法作为进程执行

进程间通信

进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。常用进程间通信方法:消息队列,套接字等

  • 消息队列通信原理:在内存中开辟空间,建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
from multiprocessing import Queue

q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象

q.put(data)
功能:向队列存入消息
参数:data  要存入的内容

q.get()
功能:从队列取出消息
返回值: 返回获取到的内容

q.full()   判断队列是否为满
q.empty()  判断队列是否为空
q.qsize()  获取队列中消息个数
q.close()  关闭队列
进程间通信示例:
from multiprocessing import Process,Queue

# 创建消息队列
q = Queue(5)

# 子进程函数
def handle():
    while True:
        cmd = q.get() # 取出指令
        if cmd == "1":
            print("\n完成指令1")
        elif cmd == "2":
            print("\n完成指令2")

# 创建进程
p = Process(target=handle,daemon=True)
p.start()

while  True:
    cmd = input("指令:")
    if not cmd:
        break
    q.put(cmd) # 通过队列给子进程