python多任务编程

意义 : 充分利用计算机的资源提高程序的运行效率

定义 : 通过应用程序利用计算机的多个核心达到同时执行多个任务的目的,一次提高计算机运行效率。

实施方案 : 多进程 多线程

并行 : 多个计算机核心在同时处理多个任务,这时多个任务间是并行关系。

并发 : 同时处理多个任务,内核在多个任务间不断的切换,达到好像都在处理运行的效果。但实际一个时间点内核只能处理其中一个任务。

进程 (Process)

定义 : 程序在计算机中的一次运行过程

程序 : 是一个可执行的文件,是静态的占有磁盘空间,不占有计算机的运行资源

进程 : 进程是一个动态过程的描述,占有计算机的资源,有一定的生命周期

  • 同一个程序的不同运行过程是不同的进程,占用资源和生命周期都不一样。

进程的创建流程
1.用户空间通过运行程序或者调用接口发起创建进程
2.操作系统接受用户请求,开始创建进程
3.操作系统分配计算机资源,确定进程状态,开辟进程空间等工作
4.操作系统将创建好的进程提供给应用程序使用

cpu时间片

如果一个进程占有计算机核心,我们称为改进程占有计算机cpu时间片。

  • 多个任务之间是争夺cpu的关系
  • 谁占有cpu最终是操作系统决定

PCB (进程控制块)
在内存中开辟的一块空间,用来记录进程的信息

  • 进程控制块是操作系统查找识别进程的标志

进程信息 : ps -aux

PID(process ID) : 在操作系统中每个进程都有一个唯一的ID号用来区别于其他进程。ID号由操作系统自动分配,是一个大于0的整数

父子进程 : 在系统中除了初始化进程,每一个进程都有一个父进程,可能有0个或者多个子进程。由此形成父子进程关系。

查看进程树 : pstree
查看父进程PID: ps -ajx

进程的状态

三态
* 就绪态 : 进程具备执行条件,等待系统分配资源
* 运行态 : 进程占有cpu处于运行状态
* 等待态 : 进程暂时不具备执行条件,阻塞等待满 足条件后再执行

五态 (三态基础上增加新建态,终止态)
* 新建态 : 创建一个新的进程,获取资源的过程
* 终止态 : 进程执行结束,资源释放回收的过程

ps -aux —> STAT

S 等待态 (可中断等待)
D 等待态 (不可中断等待)
T 等待态 (暂停状态)
R 运行态 (包含就绪态)
Z 僵尸进程

< 高优先级进程
N 优先级较低
l 有子进程的
s 会话组组长

  • 前台进程

进程优先级

作用 : 决定了一个进程的执行权限和占有资源的优先程度

查看进程优先级
top 动态查看系统中的进程信息, 用<>翻页
取值范围 -20 – 19 -20优先级最高

使用指定的优先级运行程序
nice : 指定运行的优先级

e.g.   nice -9  ./while.py   以优先级9运行
       nice --9  ./while.py  以-9优先级运行

进程特征

  1. 进程之间运行互不影响 各自独立运行
  2. 进程是操作系统资源分配的最小单位
  3. 每个进程空间独立,各自占有一定的虚拟内存

要求 :

  1. 什么是进程,进程和程序的区别
  2. 了解进程特征
  3. 清楚进程每种状态,以及状态之间的转换关系

多进程编程

import  os  

pid = os.fork()
功能 : 创建新的进程
参数 : 无
返回值: 失败返回一个负数
成功 : 在原有进程中返回新的进程的PID号,在新的进程中返回0
  • 子进程会复制父进程全部代码段,包括fork之前产生的内存空间
  • 子进程从fork的下一句开始执行,与父进程互不干扰
  • 父子进程的执行顺序是不一定的,父子进程公用一个终端显示
  • 父子进程通常会根据fork返回值得差异选择执行不同的代码。所以if结构几乎是fork的固定搭配
  • 父子进程空间独立,操作的都是本空间的内容,互不影响
  • 子进程也有自己的特性,比如PID号,PCB,命令集等

进程相关函数

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

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

进程退出

os._exit(status)
功能 : 进程退出
参数 : 进程的退出状态

sys.exit([status])
功能 : 进程退出
参数 : 数字表示退出状态,不写默认为0
字符串,表示退出时打印的内容

  • sys.exit 可以通过捕获 SystemExit异常阻止退出

孤儿进程: 父进程先于子进程退出,此时子进程就称为孤儿进程。

  • 孤儿进程会被操作系统指定的进程收养,系统进程就成为孤儿进程的新的父进程

僵尸进程: 子进程先于父进程退出,但是父进程没有处理子进程的退出状态,此时子进程就会成为僵尸进程。

  • 僵尸进程会存留少量PCB信息在内存中,大量的僵尸进程会消耗系统资源,应该避免僵尸进程产生

如何避免僵尸进程产生
* 处理子进程退出状态
pid,status = os.wait()
功能 :在父进程中阻塞等待处理子进程退出
返回值: pid 退出的子进程的PID号
status 获取子进程退出状态

pid,status = os.waitpid(pid,option)
功能 :在父进程中阻塞等待处理子进程退出
参数 : pid  -1 表示等待任意子进程退出
             >0 表示等待对应PID号的子进程退出
	option  0 表示阻塞等待
	        WNOHANG 表示非阻塞
    返回值: pid 退出的子进程的PID号
         status  获取子进程退出状态

waitpid(-1,0)  ===> wait()

* 让父进程先退出
   1. 父进程创建子进程等待子进程退出
   2. 子进程创建二级子进程后立即退出
   3. 二级子进程称为孤儿,和原来的父进程各自执行事件

multiprocessing 模块创建进程

  1. 需要将要执行的事情封装为函数
  2. 使用multiprocessing模块中Process类创建进程对象
  3. 通过对象属性设置和Process的初始化函数对进程进行设置,绑定要执行的函数
  4. 启动进程,会自动执行进程绑定的函数
  5. 完成进程的回收
Process()
功能 : 创建进程对象
参数 : name  进程名称  Process-1
        target  绑定函数 
	args  元组  给target函数按照位置传参
        kwargs  字典  给target函数按照键值对传参

p.start()
功能:启动进程
* target函数会自动执行,此时进程真正被创建

p.join([timeout])
功能 : 阻塞等待回收子进程
参数 : 超时时间

* 使用multiprocessing创建子进程,同样子进程复制父进程的全部代码段,
* 父子进程各自执行互不影响,父子进程有各自的运行空间

* 如果不使用join回收子进程则子进程退出后会成为僵尸进程
* 使用multiprocessing创建子进程往往父进程只是用来创建进程回收进程

Process进程对象属性

p.start()
p.join()

p.is_alive()  
判断进程生命周期状态,处于生命周期得到True否则返回False

p.name 进程名称 默认为Process-1
p.pid  进程的PID号

p.daemon 
默认状态False  主进程退出不会影响子进程执行
如果设置为True 则子进程会随着主进程结束而结束

* 要在start前设置
* 一般不和join一起使用

创建自定义进程类

  1. 继承Process
  2. 编写自己的__init__ ,同时加载父类init方法
  3. 重写run方法,可以通过生成的对象调用start自动运行

优点 : 可以使用计算机多核,进行任务的并发执行,提高执行效率空间独立,数据安全 运行不受其他进程影响,创建方便
缺点 : 进程的创建和删除消耗的系统资源较多

进程池技术

产生原因 : 如果有大量任务需要多进程完成,则可能需要频繁的创建删除进程,给进算计带来较多的资源消耗。

原理 : 创建适当的进程放入进程池,用来处理待处理事件,处理完毕后进程不销毁,仍然在进程池中等待处理其他事件。 进程的复用降低了资源的消耗

使用方法

  1. 创建进程池,在池内放入适当的进程
  2. 将事件加入到进程池等待队列
  3. 不断取进程执行事件,直到所有事件执行完毕
  4. 关闭进程池,回收进程
from multipeocessing import  Pool


Pool(processes)
功能 : 创建进程池对象
参数 :表示进程池中有多少进程

pool.apply_async(func,args,kwds)
功能 : 将事件放入到进程池队列
参数 : func 事件函数
        args 以元组形式给func传参
	kwds 以字典形式给func传参
返回值 : 返回一个代表进程池事件的对象

pool.apply(func,args,kwds)
功能 : 将事件放入到进程池队列
参数 : func 事件函数
        args 以元组形式给func传参
	kwds 以字典形式给func传参

pool.close()
功能: 关闭进程池

pool.join()
功能:回收进程池

pool.map(func,iter)
功能: 将要做的时间放入进程池
参数: func  要执行的函数
       iter  迭代对象
返回值 : 返回事件函数的返回值列表

进程间通信 (IPC)

原因 : 进程空间相对独立,资源无法相互获取,此时在不同进程间通信需要专门方法。

进程间通信方法 : 管道 消息队列 共享内存 信号
信号量 套接字

管道通信 Pipe

通信原理 : 在内存中开辟管道空间,生成管道操作对象,多个进程使用"同一个"管道对象进行操作即可实现通信

multiprocessing ---》 Pipe

fd1,fd2 = Pipe(duplex = True)
功能 : 创建管道
参数 : 默认表示双向管道
        如果设置为False则为单向管道
返回值 : 表示管道的两端
          如果是双向管道 都可以读写
	  如果是单向管道 则fd1只读 fd2只写

fd.recv()
功能 : 从管道读取信息
返回值: 读取到的内容

* 如果管道为空则阻塞

fd.send(data)
功能:向管道写入内容
参数: 要写入的内容
* 可以发送python数据类型

消息队列

队列 : 先进先出
通信原理 : 在内存中建立队列数据结构模型。多个进程都可以通过队列存入内容,取出内容的顺序和存入顺序保持一致

创建队列
q = Queue(maxsize = 0)
功能 : 创建消息队列
参数 : 表示最多存放多少消息。默认表示根据内存分配存          储
返回值 : 队列对象

q.put(data,[block,timeout])
功能: 向队列存储消息
参数 :data 要存的内容
       block 默认队列满时会阻塞,设置为False则非阻塞
       timeout 超时时间

data = q.get([block,timeout])
功能:获取队列消息
参数:block 默认队列空时会阻塞,设置为False则非阻塞
      timeout 超时时间
返回值 : 返回取出的内容

q.full()   判断队列是否为满
q.empty()  判断队列是否为空
q.qsize()  判断队列中消息数量
q.close()  关闭队列

共享内存

通信原理:在内存空开辟一块空间,对多个进程可见,进程可以写入输入,但是每次写入的内容会覆盖之前的内容。

obj = Value(ctype,obj)
功能 : 开辟共享内存空间
参数 : ctype  要存储的数据类型
        obj  共享内存的初始化数据
返回 :共享内存对象

obj.value 即为共享内存值,对其修改即修改共享内存

obj = Array(ctype,obj)
功能 : 开辟共享内存空间
参数 : ctype  要存储的数据格式
        obj  初始化存入的内容 比如列表,字符串
	     如果是整数则表示开辟空间的个数
返回值 : 返回共享内存对象
         * 可以通过遍历过户每个元素的值
	   e.g.  [1,2,3]  ---> obj[1] == 2
         * 如果存入的是字符串
	   obj.value 表示字符串的首地址
       	  管道         消息队列       共享内存
开辟空间   内存         内存           内存

读写方式   两端读写     先进先出       覆盖之前内容
           双向/单向

效率       一般          一般          较高

应用       多用于父     广泛灵活       需要注意
           子进程                      进行互斥操作

信号通信

一个进程向另一个进程发送一个信号来传递某种讯息,接受者根据接收到的信号进行相应的行为

kill -l 查看系统信号
kill -sig PID 向一个进程发送信号

关于信号
信号名称 信号含义 默认处理方法

SIGHUP 连接断开
SIGINT CTRU-C
SIGQUIT CTRU-
SIGTSTP CTRL-Z
SIGKILL 终止一个进程
SIGSTOP 暂停一个进程
SIGALRM 时钟信号
SIGCHLD 子进程状态改变时给父进程发出

python 发送信号

signal  

os.kill(pid,sig)
功能: 发送信号
参数: pid 目标进程
       sig 要发送的信号
import signal

signal.alarm(sec)
功能 : 向自身发送时钟信号 --》 SIGALRM
参数 : sec  时钟时间

* 进程中只能有一个时钟,第二个会覆盖第一个时间

同步执行 : 按照顺序逐句执行,一步完成再做下一步
异步执行 : 在执行过程中利用内核记录延迟发生或者准备             处理的事件。这样不影响应用层的持续执行。             当事件发生时再由内核告知应用层处理

* 信号是唯一的异步通信方法

signal.pause()
功能:阻塞等待接收一个信号

signal.signal(signum,handler)
功能: 处理信号
参数: signum  要处理的信号
       handler 信号的处理方法 
           SIG_DFL  表示使用默认的方法处理
	   SIG_IGN  表示忽略这个信号
	   func     传入一个函数表示用指定函数处理
		def func(sig,frame)
		    sig: 捕获到的信号
		    frame : 信号对象

信号量(信号灯)

原理 : 给定一个数量,对多个进程可见,且多个进程都可以操作。进程通过对数量多少的判断执行各自的行为。

multiprocessing --》 Semaphore()

sem = Semaphore(num)
功能: 创建信号量
参数: 信号量初始值
返回: 信号量对象

sem.get_value()  获取信号量值
sem.acquire() 将信号量减1  当信号量为0会阻塞
sem.release() 将信号量加1

进程的同步互斥

临界资源 :多个进程或者线程都能够操作的共享资源
临界区 : 操作临界资源的代码段

同步 : 同步是一种合作关系,为完成某个任务,多进程或者多线程之间形成一种协调,按照约定或条件执行操作临界资源。

互斥 : 互斥是一种制约关系,当一个进程或者线程使用临界资源时进行上锁处理,当另一个进程使用时会阻塞等待,直到解锁后才能继续使用。

Event  事件

multiprocessing  --》 Event

创建事件对象
e = Event()

设置事件阻塞
e.wait([timeout])

事件设置 当事件被设置后e.wait()不再阻塞
e.set()

清除设置 当事件设置被clear后 e.wait又会阻塞
e.clear()

事件状态判断
e.is_set()



Lock 锁

创建对象
lock = Lock() 

lock.acquire() 上锁  如果锁已经是上锁状态调用此函数会阻塞

lock.release() 解锁

with lock:   上锁
   ....
   ....
              解锁

线程

线程也是一种多任务编程方法,可以利用计算机多核资源完成程序的并发执行。线程又被称为轻量级的进程。

线程特征

  • 线程计算机多核分配的最小单位
  • 一个进程可以包含多个线程
  • 线程也是一个运行的过程,消耗计算机资源,多个线程共享进程的资源和空间
  • 线程的创建删除消耗的资源都要远远小于进程
  • 多个线程之间执行互不干扰
  • 线程也有自己的特有属性,比如指令集 ID
threading 模块创建线程

threading.Thread()
功能 : 创建线程对象
参数 :name  线程名称  默认 Thread-1 
       target  线程函数 
       args  元组   给线程函数位置传参
       kwargs  字典  给线程函数键值传参
 
t.start()  启动线程 自动运行线程函数
t.join([timeout])  回收线程


线程对象属性

t.is_alive()  查看线程状态
t.name  线程名称
t.setName()  设置线程名称
t.getName()  获取线程名称
threading.currentThread()  获取当前线程对象

t.daemon 属性
默认情况主线程退出不会影响分支线程执行
如果设置为True 则分支线程随主线程退出

设置方法:
t.daenon = True
t.setDaemon(True)

判断属性值
t.isDaemon()

* 要在start前设置,不会和join同用

创建自己的线程类
步骤:
1.继承Thread
2.加载Thread中的__init__
3.重写run方法

线程通信

通信方法: 多个线程共享进程的空间,所以线程间通            信使用全局变量完成。

注意事项: 线程间使用全局变量往往要同步互斥机制            保证通信安全

线程同步互斥方法

线程的event
e = threading.Event()  创建事件对象
e.wait([timeout])  如果e为设置状态则不阻塞否则阻塞
e.set()  将e变为设置状态
e.clear()  清除设置

线程锁
lock = threading.Lock()  创建锁对象
lock.acquire()  上锁
lock.release()  解锁

* 也可以通过with上锁,上锁状态调用acquire会阻塞

多线程并发

threading 的多线程并发

对比多进程并发:
* 消耗资源较少
* 线程应该更注意共享资源的操作
* 在python中应该注意GIL问题,网络延迟较高,线程并 发也是一种可行的办法

实现步骤
1. 创建套接字,绑定监听
2. 接收客户端请求,创建新的线程
3. 主线程继续接收其他客户端连接
4. 分支线程启动对应的函数处理客户端请求
5. 当客户端断开,则分支线程结束

cookie

import traceback

traceback.print_exc()
功能 : 更详细的打印异常信息


集成模块的使用
python3 socketserver

功能 : 通过模块的不同类的组合完成多进程/多线程 的           tcp/udp的并发

StreamRequestHandler  处理tcp套接字请求
DatagramRequestHandler  处理udp套接字请求

TCPServer  创建tcp server
UDPServer  创建udp server

ForkingMixIn   创建多进程
ForkingTCPServer -->  ForkingMinIn + TCPServer
ForkingUDPServer -->  ForkingMinIn + UDPServer

ThreadingMixIn  创建多线程
ThreadingTCPServer --> ThreadingMinIn + TCPServer
ThreadingUDPServer --> ThreadingMinIn + UDPServer
 

HTTPServer V2.0

  1. 接收客户端请求
  2. 解析客户端请求
  3. 组织数据,形成HTTP response
  4. 将数据发送给客户端

升级

  1. 采用多线程并发接收多个客户端请求
  2. 基本的请求解析,根据请求返回相应的内容
  3. 除了可以请求静态网页,也可以请求简单的数据
  4. 将功能封装在一个类中

技术点 :

  1. socket tcp 套接字
  2. http协议的请求响应格式
  3. 线程并发的创建方法
  4. 类的基本使用

协程基础

定义 : 纤程,微线程。协程的本质是一个单线程程序,所以协程不能够使用计算机多核资源。

作用 : 能够高效的完成并发任务, 占用较少的资源。因 此协程的并发量较高

原理 : 通过记录应用层的上下文栈区,实现在运行中进行上下文跳转,达到可以选择性地运行想要运行的部分,以此提高程序的运行效率。

优点 : 消耗资源少
无需切换开销
无需同步互斥
IO并发性好

缺点 : 无法利用计算机多核

yield —》 协程实现的基本关键字

greenlet

greenlet.greenlet()  生成协程对象
gr.switch() 选择要执行的协程事件

gevent

1. 将协程事件封装为函数
2. 生成协程对象
  gevent.spawn(func,argv)
  功能 : 生成协程对象
  参数 : func  协程函数
          argv  给协程函数传参
  返回值 : 返回协程对象

3.回收协程
  gevent.joinall()
  功能 : 回收协程
  参数: 列表 将要回收的协程放入列表

 gevent.sleep(n)
 功能: 设置协程阻塞,让协程跳转
 参数: n  阻塞时间

from gevent import monkey
monkey.patch_all()
功能: 修改套接字的IO阻塞行为

* 必须在socket导入之前使用
发布了49 篇原创文章 · 获赞 6 · 访问量 5003

猜你喜欢

转载自blog.csdn.net/qq_43959027/article/details/102991346