一、多任务编程
1. 意义: 充分利用计算机多核资源,提高程序的运行效率。
2. 实现方案 :多进程 , 多线程
3. 并行与并发
并发 : 同时处理多个任务,内核在任务间不断的切换达到好像多个任务被同时执行的效果,实际每个时刻只有一个任务占有内核。
并行 : 多个任务利用计算机多核资源在同时执行,此时多个任务间为并行关系。
二、进程(process)
进程理论基础
1. 定义 : 程序在计算机中的一次运行。
1> 程序是一个可执行的文件,是静态的占有磁盘。
2> 进程是一个动态的过程描述,占有计算机运行资源,有一定的生命周期。
2. 系统中如何产生一个进程
【1】 用户空间通过调用程序接口或者命令发起请求
【2】 操作系统接收用户请求,开始创建进程
【3】 操作系统调配计算机资源,确定进程状态等
【4】 操作系统将创建的进程提供给用户使用
3.进程基本概念
cpu时间片:如果一个进程占有cpu内核则称这个进程在cpu时间片上。
PCB(进程控制块):在内存中开辟的一块空间,用于存放进程的基本信息,也用于系统查找识别进程。
进程ID(PID): 系统为每个进程分配的一个大于0的整数,作为进程ID。每个进程ID不重复。
Linux查看进程ID : ps -aux
父子进程 : 系统中每一个进程(除了系统初始化进程)都有唯一的父进程,可以有0个或多个子进程。父子进程关系便于进程管理。
查看进程树: pstree
进程状态
三态
就绪态 : 进程具备执行条件,等待分配cpu资源
运行态 : 进程占有cpu时间片正在运行
等待态 : 进程暂时停止运行,让出cpu
五态 (在三态基础上增加新建和终止)
新建 : 创建一个进程,获取资源的过程
终止 : 进程结束,释放资源的过程
状态查看命令 : ps -aux --> STAT列
S 等待态
R 执行态
D 等待态
T 等待态
Z 僵尸
< 有较高优先级
N 优先级较低
+ 前台进程
s 会话组组长
l 有多线程的
进程的运行特征
【1】 进程可以使用计算机多核资源
【2】 进程是计算机分配资源的最小单位
【3】 进程之间的运行互不影响,各自独立
【4】 每个进程拥有独立的空间,各自使用自己空间资源
基于fork的多进程编程
fork使用
pid = os.fork()
功能: 创建新的进程
返回值:整数,如果创建进程失败返回一个负数,如果成功则在原有进程中返回新进程的PID,在新进程中返回0
注意
1>子进程会复制父进程全部内存空间,从fork下一句开始执行。
2>父子进程各自独立运行,运行顺序不一定。
3>利用父子进程fork返回值的区别,配合if结构让父子进程执行不同的内容几乎是固定搭配。
4>父子进程有各自特有特征比如PID PCB 命令集等。
5>父进程fork之前开辟的空间子进程同样拥有,父子进程对各自空间的操作不会相互影响。
进程相关函数
os.getpid()
功能: 获取一个进程的PID值
返回值: 返回当前进程的PID
os.getppid()
功能: 获取父进程的PID号
返回值: 返回父进程PID
os._exit(status)
功能: 结束一个进程
参数:进程的终止状态
sys.exit([status])
功能:退出进程
参数:整数 表示退出状态
字符串 表示退出时打印内容
孤儿和僵尸
1. 孤儿进程 : 父进程先于子进程退出,此时子进程成为孤儿进程。
特点: 孤儿进程会被系统进程收养,此时系统进程就会成为孤儿进程新的父进程,孤儿进程退出该进程会自动处理。
2. 僵尸进程 : 子进程先于父进程退出,父进程又没有处理子进程的退出状态,此时子进程就会称为僵尸进程。
特点: 僵尸进程虽然结束,但是会存留部分PCB在内存中,大量的僵尸进程会浪费系统的内存资源。
3. 如何避免僵尸进程产生
1>使用wait函数处理子进程退出
pid,status = os.wait()
功能:在父进程中阻塞等待处理子进程退出
返回值: pid 退出的子进程的PID
status 子进程退出状态
pid,status = os.waitpid(pid,option)
功能: 在父进程中处理子进程退出状态
参数: pid -1 表示等待任意子进程退出
>0 表示等待指定的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞
返回值:pid 退出的子进程的PID
status 子进程退出状态
2>创建二级子进程处理僵尸
【1】 父进程创建子进程,等待回收子进程
【2】 子进程创建二级子进程然后退出
【3】 二级子进程称为孤儿,和原来父进程一同执行事件
3>通过信号处理子进程退出
原理: 子进程退出时会发送信号给父进程,如果父进程忽略子进程信号,则系统就会自动处理子进程退出。
方法: 使用signal模块在父进程创建子进程前写如下语句 :
import signal
signal.signal(signal.SIGCHLD,signal.SIG_IGN)
特点 : 非阻塞,不会影响父进程运行。可以处理所有子进程退出
multiprocessing 模块创建进程
进程创建方法
1. 流程特点
【1】 将需要子进程执行的事件封装为函数
【2】 通过模块的Process类创建进程对象,关联函数
【3】 可以通过进程对象设置进程信息及属性
【4】 通过进程对象调用start启动进程
【5】 通过进程对象调用join回收进程
2. 基本接口使用
Process()
功能 : 创建进程对象
参数 : target 绑定要执行的目标函数
args 元组,用于给target函数位置传参
kwargs 字典,给target函数键值传参
p.start()
功能 : 启动进程
注意:启动进程此时target绑定函数开始执行,该函数作为子进程执行内容,此时进程真正被创建
p.join([timeout])
功能:阻塞等待回收进程
参数:超时时间
注意:
1>使用multiprocessing创建进程同样是子进程复制父进程空间代码段,父子进程运行互不影响。
2>子进程只运行target绑定的函数部分,其余内容均是父进程执行内容。
3>multiprocessing中父进程往往只用来创建子进程回收子进程,具体事件由子进程完成。
4>multiprocessing创建的子进程中无法使用标准输入
3. 进程对象属性
p.name 进程名称
p.pid 对应子进程的PID号
p.is_alive() 查看子进程是否在生命周期
p.daemon 设置父子进程的退出关系
1>如果设置为True则子进程会随父进程的退出而结束
2>要求必须在start()前设置
3>如果daemon设置成True 通常就不会使用 join()
进程池实现
1. 必要性
【1】 进程的创建和销毁过程消耗的资源较多
【2】 当任务量众多,每个任务在很短时间内完成时,需要频繁的创建和销毁进程。此时对计算机压力较大
【3】 进程池技术很好的解决了以上问题。
2. 原理
创建一定数量的进程来处理事件,事件处理完进 程不退出而是继续处理其他事件,直到所有事件全都处理完毕统一销毁。增加进程的重复利用,降低资源消耗。
3. 进程池实现
【1】 创建进程池对象,放入适当的进程
from multiprocessing import Pool
Pool(processes)
功能: 创建进程池对象
参数: 指定进程数量,默认根据系统自动判定
【2】 将事件加入进程池队列执行
pool.apply_async(func,args,kwds)
功能: 使用进程池执行 func事件
参数: func 事件函数
args 元组 给func按位置传参
kwds 字典 给func按照键值传参
返回值: 返回函数事件对象
【3】 关闭进程池
pool.close()
功能: 关闭进程池
【4】 回收进程池中进程
pool.join()
功能: 回收进程池中进程
进程间通信(IPC)
1. 必要性: 进程间空间独立,资源不共享,此时在需要进程间数据传输时就需要特定的手段进行数据通信。
2. 常用进程间通信方法
管道 消息队列 共享内存 信号 信号量 套接字
1>管道通信(Pipe)
1. 通信原理:在内存中开辟管道空间,生成管道操作对象,多个进程使用同一个管道对象进行读写即可实现通信
2. 实现方法
from multiprocessing import Pipe
fd1,fd2 = Pipe(duplex = True)
功能: 创建管道
参数:默认表示双向管道
如果为False 表示单向管道
返回值:表示管道两端的读写对象
如果是双向管道均可读写
如果是单向管道fd1只读 fd2只写
fd.recv()
功能 : 从管道获取内容
返回值:获取到的数据
fd.send(data)
功能: 向管道写入内容
参数: 要写入的数据
2>消息队列
1.通信原理:在内存中建立队列模型,进程通过队列将消息存入,或者从队列取出完成进程间通信。
2. 实现方法
from multiprocessing import Queue
q = Queue(maxsize=0)
功能: 创建队列对象
参数:最多存放消息个数
返回值:队列对象
q.put(data,[block,timeout])
功能:向队列存入消息
参数:data 要存入的内容
block 设置是否阻塞 False为非阻塞
timeout 超时检测
q.get([block,timeout])
功能:从队列取出消息
参数:block 设置是否阻塞 False为非阻塞
timeout 超时检测
返回值: 返回获取到的内容
q.full() 判断队列是否为满
q.empty() 判断队列是否为空
q.qsize() 获取队列中消息个数
q.close() 关闭队列
3>共享内存
1. 通信原理:在内中开辟一块空间,进程可以写入内容和读取内容完成通信,但是每次写入内容会覆盖之前内容。
2. 实现方法
from multiprocessing import Value,Array
obj = Value(ctype,data)
功能 : 开辟共享内存
参数 : ctype 表示共享内存空间类型 ‘i‘ ‘f‘ ‘c‘
data 共享内存空间初始数据
返回值:共享内存对象
obj.value 对该属性的修改查看即对共享内存读写
obj = Array(ctype,data)
功能: 开辟共享内存空间
参数: ctype 表示共享内存数据类型
data 整数则表示开辟空间的大小,其他数据类型 表示开辟空间存放
返回值:共享内存对象
Array共享内存读写: 通过遍历obj可以得到每个值,直接可以通过索引序号修改任意值。
* 可以使用obj.value直接打印共享内存中的字节串
4>本地套接字
1. 功能 : 用于本地两个程序之间进行数据的收发
2. 套接字文件 :用于本地套接字之间通信时,进行数据传输的介质。
3. 创建本地套接字流程
【1】 创建本地套接字
sockfd = socket(AF_UNIX,SOCK_STREAM)
【2】 绑定本地套接字文件
sockfd.bind(file)
【3】 监听,接收客户端连接,消息收发
listen()-->accept()-->recv(),send()
5>信号量(信号灯集)
1. 通信原理:给定一个数量对多个进程可见。多个进程都可以操作该数量增减,并根据数量值决定自己的行为。
2. 实现方法
from multiprocessing import Semaphore
sem = Semaphore(num)
功能 : 创建信号量对象
参数 : 信号量的初始值
返回值 : 信号量对象
sem.acquire() 将信号量减1 当信号量为0时阻塞
sem.release() 将信号量加1
sem.get_value() 获取信号量数量
三、线程编程(Thread)
线程基本概念
1. 什么是线程
【1】 线程被称为轻量级的进程
【2】 线程也可以使用计算机多核资源,是多任务编程方式
【3】 线程是系统分配内核的最小单元
【4】 线程可以理解为进程的分支任务
2. 线程特征
【1】 一个进程中可以包含多个线程
【2】 线程也是一个运行行为,消耗计算机资源
【3】 一个进程中的所有线程共享这个进程的资源
【4】 多个线程之间的运行互不影响各自运行
【5】 线程的创建和销毁消耗资源远小于进程
【6】 各个线程也有自己的ID等特征
threading模块创建线程
【1】 创建线程对象
from threading import Thread
t = Thread()
功能:创建线程对象
参数:target 绑定线程函数
args 元组 给线程函数位置传参
kwargs 字典 给线程函数键值传参
【2】 启动线程
t.start()
【3】 回收线程
t.join([timeout])
线程对象属性
t.name 线程名称
t.setName() 设置线程名称
t.getName() 获取线程名称
t.is_alive() 查看线程是否在生命周期
t.daemon 设置主线程和分支线程的退出关系
t.setDaemon() 设置daemon属性值
t.isDaemon() 查看daemon属性值
daemon为True时主线程退出分支线程也退出。要在start前设置,通常不和join一起使用。
自定义线程类
1. 创建步骤
【1】 继承Thread类
【2】 重写__init__方法添加自己的属性,使用super加载父类属性
【3】 重写run方法
2. 使用方法
【1】 实例化对象
【2】 调用start自动执行run方法
【3】 调用join回收线程
原文地址:https://www.cnblogs.com/yuxiangyang/p/10885385.html