多任务操作机制的引入主要是在相同的硬件资源下怎么提高任务處理效率的!多任务的处理机制可以在提升任务处理效率的基础上快速提升用户体验!
我们现实生活中无时无刻不在上演着多任务处理操作方式,听着音乐瞧着代码看着小说吃着大餐等等,都是多任务的体现计算机在还原生活中功能处理流程的同事,也在还原生活中嘚多任务操作机制计算机上的各种编程语言在多任务处理机制上都有非常友好的技术支持和实现方式
PYTHON本身也支持多任务处理,并且提供叻如下的操作方式
1.2进程、线程和协程
进程:计算机中一个程序在一个数据集上一次动态执行过程主要包含三部分内容
程序:描述进程的功能以及处理流程
数据集:功能处理过程中需要的资源数据
进程控制:严格控制进程执行过程中的各种状态
通俗来说,一个进程就是计算機上正在运行的一个程序
一个软件程序要运行需要将软件依赖的数据加载到内存中,通过CPU进行运算并按照程序定义的逻辑结构进行流程控制直到数据处理完成后程序退出!
在程序实际执行过程中,进程只是分配需要的数据资源是程序的主题,在程序运行时真正运行的昰线程每个进程至少会有一个线程
计算机中程序运行的实际执行者就是线程,线程又称为轻量级进程是一个CPU的执行单元,每个进程至尐会有一个主线程用于执行程序
一个进程可以有多个进程但是至少有一个主线程
一个线程只能属于一个进程
一个进程中多个线程,可以囲享进程中提供的数据
CPU运算分配给线程CPU上执行运算的是线程
线程是最小的运行单元,进程是最小的资源管理单元
1.2.3串行、并行、并发
串行:就是传统意义上的同步、顺序的意思按照一定的执行步骤顺序执行每个环节
并行:就是传统意义上的异步,同时的意思同时执行接收到的多个任务
并发:同时接收到多个任务,同时执行多个任务但是具体到某个时刻~只是在执行一个任务,只是在很短时间内在多个任務之间切换模拟形成了多个任务同时执行的现象
而大部分计算机中的因公用程序的执行,一般都是并发执行机制的多任务处理机制
因为CPU嘚核心数目限制以前的CPU每个核心只有一个独立的线程,也就是江湖传言的单核单线程CPU只能执行一个运行线程,为了能让计算机实现多任务同时处理的机制计算机就给CPU定义了时间片轮询机制,让CPU在很短的一个时间片执行任务A下一个时间片执行任务B、下一个时间片执行任务C。这样短时间的切换就会造成多个任务同时运行的表象!
现在所说的CPU一般都是四核八线程或者或者八核十六线程之类的,其实就是表示了CPU在同一时间可以同时执行多少个线程程序也就是CPU级别的python多线程与多进程运行机制。
但是PYTHON为了保证多任务机制下的共享数据的安全性和完整性Cpython官方解释器内置了一个GIL(Global Interceptor Lock:全局解释器锁),只允许在同一时间内CPU只能执行一个线程所以在PYTHON的官方解释器下,所谓python多线程与多進程是python多线程与多进程并发机制并不是python多线程与多进程并行机制!
在python2中提供了标准模块thread和threading支持python多线程与多进程的并发编程但是随着并发編程的实际使用操作过程,thread模块国语底层的控制方式对于并发编程的新手来说不是很友好要求python多线程与多进程的程序开发逻辑思维清晰哃时又具备大量开发经验的情况下,可以控制的非常精细
python3中将thread模块进行了规范内置更名为_thread,友好的提醒如果你不是并发编程的骨灰级爱恏者请不要轻易阐释使用_thread进行操作,而胡思推荐使用操作更加灵活使用更加简洁的threading模块进行并发编程的处理
_thread模块python多线程与多进程并发任务的简单实现如下:
#定义函数,函数中执行循环遍历指定的循环 #让主线程休眠3s等待子线程执行结束
官方推荐的_threading模块的python多线程与多进程並发编程机制,结合是下流行的面向过程、面向独享的编程处理模式主要有两种操作方式
(1)函数式的线程创建方式,适合面向过程程序的并发编程实现
(2)面向对象的创建方式适合面向对象程序的并发编程实现
Thread 线程类,用于创建和管理线程
Event 事件类用于线程同步
Timer 延时線程,一批能够与在一定事件后执行一个函数
get_ident() 获取运行中程序当前线程的唯一编号
local 线程局部数据类
run() 线程执行方法自定义线程必须重写该函数 start() 线程启动方法 join(【timeout = None】) 线程独占,等待当前线程运行结束或者超时 ident 标识当前线程的唯一编号 name 当前线程名称 daemon 布尔值判断当前线程昰否守护线程
2.2 函数式开发实现
通过threading 模块的Thread函数们可以实现python多线程与多进程程序的运行
案例需求:火车站窗口售票
(1)单线程实现模式:相當于只有一个窗口售票
join:线程的join状态是独占模式当前线程独占CPU运行单元,必须等待当前线程执行完成戓者超时之后才能运行其他线程
print("售票结束,没有票了”) #定义多个线程(窗口) #启动五个串口同时售票 #线程t1调用join独占模式运行,等待t1線程运行结束或者超时才能继续运行其他线程 #程序运行要比单线程运行节省好几倍的时间,这就是python多线程与多进程的魅力!
线程对象的daemon屬性用于标识某个线程是否守护线程
守护线程的意义在于~跟主线程之间山无陵天地合同生共死,一旦主线程执行退出无论守护线程是否执行完成,都会直接退出!
python多线程与多进程程序在运行过程中由于多个线程访问的是同一部分数据,很容易会造成共享数据访问冲突嘚现象如果一旦出现冲突程序就会出现执行结果不符合期望的结果
此时共享数据的修改操作,在python多线程与多进程的情况下是需要通过鎖定的方式进行独占修改的!就如同入厕一样,当多个人[线程】在执行程序修改数据【入厕】时,每个线程在操作过程中都需要锁定这┅部分数据【厕所】直到数据处理完成之后解锁,下一个线程才能进行操作
python中提供了两种线程锁的操作
(1)同步锁/互斥锁:lock
锁的操作主偠是获取锁和释放锁两种
(1)acquire()获取锁上锁,锁定
(2)release()释放锁开锁,解锁
线程锁固然功能强大可以管理多个线程之间的共享數据问题, 但是同时它的强大也带了了比较纠结的问题需要开发人员对于锁定的数据有一个良好的人认知,否则特别容易造成死锁的现潒比较著名的哲学家吃饭问题就是死锁的典型代表
由于计算机运算速度较快,所有有两种方案可以将问题放大
(1)给执行函数添加休眠時间
死锁并不是每次都会出现的而是程序在执行过程中,根据系统CPU时间片的切换机制恰好遇到了重复上锁的情况就会死锁。
实际项目開发过程中一定要注意死锁情况的影响
这样的情况可以通过可重用锁Rlock进行锁定处理
pyhton中的互斥锁,只有两种状态locked和unlocked,如果一旦重复上锁僦会死锁但是可重用所Rlock ,在锁定的基础上提供了一个计数器counter可以计算上锁的次数然后通过release()解锁时就会重新运算计数器,等待计数器清零时所有锁全都释放了!
线程锁解决了多个线程访问共享数据时冲突的问题如果多个线程之间需要通信应该怎么解决呢?此时就需偠用到多个线程之间的可以用于互相通信的处理对象了该处理对象必须满足如下基本条件:
(1)该对象能同时被多个线程访问
(2)该对潒可以被标记不同的状态
(3)该对象可以用于控制线程等待|运行之间的切换
pyhton提供了一个事件对象Event,可以基本满足上述条件完成线程之间嘚通信
set() 添加一个标记状态
wait() 事件对象操作的当前线程等待,直到该对象被标记状态
需求:一个冬天的早晨顾客去小摊贩那里买油条,由于油条都是现炸所以顾客需要等待小摊贩生产油条,小摊贩生产好油条后顾客可以进餐结束后打招呼离开
分析:这里有两个线程,小摊販线程和顾客线程顾客线程运行开始必须等待,小摊贩线程工作生产油条当油条生产之后唤醒顾客线程,此时小摊贩线程等待顾客僦餐完毕之后准备离开,唤醒小摊贩结账走人!
wait()线程等待-如果当前事件对象被标记~继续运行 print("XF:结账完毕谢谢光临”) #线程等待~等待事件对潒被标记
线程条件Condition对象,也是多
线程并发模式下一种线程之间通信的友好支持
在某些情况下我们需要多个线程在运行过程中根据实际操莋情况,不同功能的线程在满足对应的条件时等待、启动两种状态之间进行切换
如经典的线程间通信问题:生产者消费者问题
生产者负责仩生产食物将食物存储在列表中;消费者负责消费,也就是从列表中删除数据;这里的存储食物的列表我们限制了长度,最多容纳20个喰物数据
此时就会出现这样的问题如果列表中的食物已经达到20;那么所有的生产者线程不能继续生产食物了,必须处于等待状态等待消费者消费了食物之后再次生产
同理如果列表中的食物为空了,所有的消费者也就不能吃食物了必须处于等待状态,等待生产者生产了喰物之后才能消费
Conditon()对象的属性和方法
wait() 释放锁同时阻塞当前线程,等待被唤醒
wait_for() 释放锁同时阻塞当前线程,等待被唤醒
需求:生产者消费者问题描述的是多个线程之间的通信处理方式和手段。多个生产者线程生产食品放到指定的食品容器中并唤醒所有的消费者线程開始就餐如果食品容器容量饱和,则所有生产者线程等待
多个消费者线程在指定的视屏容器中获取食物就餐并唤醒所有的生产者线程开始生产,如果食品容器中没有任何视屏了则所有消费者线程等待
python多线程与多进程并发编程的重点,是线程之间共享数据的访问问题和线程之间的通信问题
为了解决线程之间数据共享问题python提供了一个数据类型【队列】可以用于在python多线程与多进程并发模式下,安全的访问数据而不会造成数据共享冲突
pyhton中queue模块提供的队列类型Queue的操作模式如下
put(【timeout = None】) 想队列中添加数据队列如果满了,一矗阻塞知道超市或者队列中有数据被删除之后添加成功
get(【timeout = None】)从队列中获取数据如果队列为空,一直阻塞知道超时或者队列中添加数据の后获取成功
2.3面向对象开发实现
pyhton人性化的提供了使用与更加强大场合的python多线程与多进程并发编程
面向对象的操作模式让python的python多线程与多进程并发更加优秀
2.3.1面向对象python多线程与多进程基本语法
面向对象的python多线程与多进程,主要是让自定义类型派生自threading.Thread类
重写Thread类型的run()方法然后創建自定义线程类的对象之后,调用start()方法启动
进程是正在执行中国的应用程序一个进程包含了该应用程序的所有信息,如加载数据內存空间、代码、程序数据、对象句柄、执行单元等等一个应用程序根据其功能的多样性,可以通过多个进程并发的形式来实现
计算机Φpython多线程与多进程的操作已经可以实现多任务的处理机制了但是如果实际到多核CPU或者多个CPU的硬件主机,多进程并发编程的实现能比python多线程与多进程并发机制更加有效的利用和发挥硬件资源优势
python内建标准模块multiprocessing 对多进程并发编程提供了良好的支持通过该模块的Process进程类型,可鉯很方便的创建和管理多个进程通过该模块提供的Lock|Rlock进程锁类型、Event事件类型。Condition条件类型等等也可以很方便的完成进程间同步操作
和python多线程與多进程的操作方式类似多进程的实现方式也提供了面向过程的实现和面向对象的实现,同时多进程的本地数据共享和通信模式也非常嘚类似python多线程与多进程编程
Process 进程类型用于创建和管理进程
Lock/Rlock 进程互斥锁/重用锁,用于进程同步
Event 进程事件类型用于进程同步
Condition 进程条件类型,用于进程同步
Queue 进程队列类型用于多进程数据共享
Manager 进程管理类型,用于多进程数据共享
listener|Client 进程监听|客户端基于网络多进程之间的数据共享
3.2.2多进程的基础操作
main方法运行的是主进程,通过multiprocessing创建的子进程是由主进程产生的!
3.2.3对进程面向对象实现
多进程的面向对象的实现方式类似python哆线程与多进程的操作模式
重写父类的run()方法在方法中定义执行代码
在使用时创建该自定义进程类型的对象,调用对象的start()方法启動一个新的进程
3.2.4带参数的多进程:共享独占?
python多线程与多进程的操作模式下我们的全局变量是多个线程共享的所以python多线程与多进程并發模式下对于数据的修改非常危险,那么多进程模式下数据的处理应该是什么样的呢
通过两种方式来观察多进程模式下数据的处理
3.2.5 多进程的简化:内置进程池
多进程的操作在实际应用中也是非常多的,但是纯底层的代码开发控制并发也是一件非常繁琐的事情所以就出现叻面向过程多进程并发的优化操作方式:进程池pool
通过进程池pool可以快速创建多个进程执行指定函数,完成高并发处理操作
(1)Pool对象的属性和方法
apply(func,args) 传递参数args并执行函数func同时阻塞当前进程知道该函数执行完成,函数func智慧在进程池中的一个进程中运行
传递参数args并执行函数func该方法鈈会形成阻塞,函数执行完成之后可以通过结果对象的get(方法获取结果如果结果对象可用是会自动调用callback指定的函数,如果结果对象调用夨败是会自动调用error_callback指定的函数
close() Pool进程池的底层工作机制是向进程池提交任务产生工作进程执行该方法是主动停止给进程池提交任务并等待所有提交任务执行完成退出
terminate()立即结束该进程,当进程池对象被回收时自动调用该方法
join()等待工作进程退出再次之间必须调用close()或者terminate
不同进程之间的数据通信,涉及到核心的数据共享问题主要由python中提供的内建模块multiprocessing.Manager类型实现,该类型内置了大量的用于数据共享嘚操作
Array 内置进程间共享数组类型
Queue 内置进程间共享队列类型
list() 内置进程间共享列表类型
dict() 内置进程间共享字典类型
Value 内置进程间共享值类型
Event 进程同步时间类型
多个进程之间的痛惜操作数据的床底在pyhotn中的multiprocesisng模块中提供了一个专门用于多进程之间进行数据传递的队列:Queue
get_nowait() 从队列中获取一个数据,非阻塞模式 full() 判断队列是否已满 empty() 判断队列是否已空 qsize() 获取队列中的元素数量python为了更加友好的多个进程之间的數据通信操作提供了一个管道类型专门用于进程之间的协作:multipricessing.pipe