同一时间在内存中同时加载多个程序数据,其内存之间相互隔离
切换+保存状态
切换的两种情况:
1.一个进程遇到了IO操作时,切换到另一个进程,
2.时间片用完后,也会被强行切换
多道技术的出现使计算机可以并发执行任务
正在运行的程序 ,是一个资源单位, 包含程序运行的所有资源
为了并发的执行多个任务,例如TCP中客户端的并发处理
创建Process实例
继承Process类
注意:开启进程的代码必须放在 判断下面 , 因为windows平台开启子进程时,会导入代码执行一遍 来回去要执行的任务
被守护进程结束时守护进程也会随之结束
join 提高子进程的优先级 使得子进程先于父进程执行 父进程需要等待子进程完成后才能继续执行
daemon 设置为守护进程
is_alive 是否存活
pid 进程id
terminate 终止进程
exitcode 获取进程的退出码
name 名字
孤儿 父进程先于子进程结束了,子进程会被操作系统接管
僵尸 在linux有一个机制,可以保证父进程在任何时候都可以访问到子进程的一些信息,所以子进程结束后并不会立即清除所有数据 ,这时候就是僵尸进程
僵尸进程会占用一些系统资源,需要父进程调用waitpid来进行清理,
python会自动清理僵尸进程
进程间通讯
因为每个进程之间内存是物理隔离,很多时候我们需要将数据讲给另外一个进程 ,例如:美团要把订单信息交给支付宝
1.共享文件
特点:数据量没什么限制,但是读写速度慢
2.共享内存区域
特点:数据量不能太大,速度快
3.管道
单向通讯,传输的是二进制
4.socket
编程较复杂
主要方式:共享内存
1.Manager 提供一系列常用的数据结构,但是没有处理锁的问题
2.进程Queue 是一种特殊的容器,先进先出,并且进程队列支持IPC 已经处理好锁了
相互排斥的锁,mutex
锁是什么: 本质就是一个标志,可以限制代码是否能够执行
为什么需要锁:多个进程要操作同一个资源时,可能造成数据错乱
加锁会导致并发变成串行,降低了效率,保证了数据安全
join会使得整个进程代码全部串行,并且主进程也无法继续执行
锁可以控制部分代码串行,其余任然并发,效率比join高
锁的粒度越小效率越高
要解决的问题,生产者用户消费者处理能力不平衡, 如果串行执行任务 效率低下
解决的方案:
1.将生产者与消费者节考耦合
2.将双方并发执行
3.提供一个共享的容器
4.生产者将数据放入容器
5.消费者从容器获取数据来处理
是否可以使用多线程来完成生产者消费者模型 必须可以
在线程中需不需要使用队列呢?? 建议使用
数据出了问题,一张票 卖给了多个人 ,原因就是因为并发了
加锁解决: 将并发修改的代码加锁变成串行
一个进程至少包含一个线程,称之为主线程 ,是由操作系统自动开启的
运行过程中自己开启的线程 称之为子线程
线程间没有父子关系 , 例如a-b-c b和c都是a的子线程
开启速度快,开销小
同一个进程中所有线程数据共享
守护线程会在所有非守护线程结束时随之结束,当然守护线程可能提前结束了
与进程一样,开线程的代码可以放任何位置
currentthread() 获取当前的线程对象
enumerate() 获取所有运行中的线程对象
active_count 获取存活的线程数量
queue
Queue 普通队列
LifoQueue 先进后出队列 模拟堆栈
PriorityQueue 优先级队列 可以比较大小的数据都能存到其中 取出时按照从小到大取出
运算符重载 可以使自定义对象支持 算术运算符
Lock 互斥锁
Rlock 递归锁 同一个线程可以多次锁定或解锁 ,锁了几次就解几次
semaphore 信号量 可以限制同一时间多少线程可以并发执行
死锁问题 当一个资源的访问,需要具备多把锁时,然而不同的锁被不同线程持有了,陷入相互等待中
1.,尽量使用一个锁 , 设置超时 释放手里的锁,
2.抢锁时 按顺序抢
GIL全程 全局解释器锁 ,是一把互斥锁,是非常重要的,为了防止多个本地线程同一时间执行python的字节码,
因为Cpython的内存管理不是线程安全的(非线程安全的),越来越多的特性依赖于这把锁, 如果去掉这个锁的话,
会有很代码需要重构, 并且需要程序自己来处理很多的安全问题,这是非常复杂的
cpython多个线程不能并行,丧失了多核优势
保证了线程安全,
首先判断任务的类型 ,分IO密集型 计算密集型 ? 是IO密集型任务,使用多线程即可,由于大部分时间消耗在IO等待上了,所以影响不大
计算密集型,只能开启多进程
GIL只能 保证解释器级别数据安全,如果我们自己开启了一些不属于解释器的资源例如文件. 必须自己加锁来处理
拿到解释器要执行代码时立即加锁
遇到IO时解锁
CPU时间片用完了 注意解释器的超时时间 与CPU的超时时间不同 为100nm
进程池 线程池
池即容器
线程池 即存储线程的容器
为什么使用线程池
1.可以限制线程 数量 通过压力测试 来得出最大数量
2.可以管理线程的创建以及销毁
3.负责任务的分配
使用
创建池
submit提交任务 异步任务 将返回future对象 调用add_done_callback 可以添加回调函数
在任务结束时还会自动调用 回调函数并传入 future本身, 调用result()可以拿到任务的结果
不常用的两种方式
shutdown 可以关闭线程池,会阻塞直到所有任务全部完成
直接调用result 如果任务没有完成会进入阻塞状态
同步: 任务提交后必须原地等待任务执行结,才能继续执行
异步: 提交任务后可以立即执行后续代码
异步效率高
如何实现异步:
多线程,多进程
同步异步指的是 任务的执行方式
异步回调 本质就是一个普通函数,该函数会在任务执行完成后自动被调用
线程池, 谁有空谁处理
进程池,都是在父进程中回调
协程本质是在单线程下实现并发
协程是一种轻量级线程,也称为微线程,可以由应用程序自己来控制调度
好处:
可以再一个任务遇到IO操作时,自主切换到自己进程中其他线程
如果任务足够多,就可以充分利用CPU的时间片
缺点:
仅适用于IO密集型任务 ,计算密集型,如果是单线程话的串行效率更高 ,建议使用多进程来处理
当单个任务耗时较长时 协程效率反而不高
我们目的就是尽可能的提高效率
进程 线程 协程
可以使 多进程 + 线程 + 协程 UWSGI
协程对比多线程:
线程池可以解决一定的并发数量,但是如果并发量超过了机器能承受最大限制,线程池就出现瓶颈了
gevent 需要自己安装
1.先打补丁 (本质是将原本阻塞的代码替换成非阻塞的代码)
2.gevent.spawn(任务) 来提交任务
3.必须保证主线不会结束 使用join 或是join all
原文:https://www.cnblogs.com/huanghongzheng/p/10999845.html