-
作业与进程的区别
-
前者是由用户提交,后者是由系统自动生成;前者以用户任务为单位,后者是操作系统控制的单位.
-
作业 = 程序 + 数据 + 说明书, 进程 = 程序 + 数据 + PCB
-
一个作业可以分为多个进程来完成
-
-
进程与线程
-
进程是资源分配的最小单位, 线程是CPU调度的最小单位
-
一个进程下可包含1至多个线程
-
一个进程有独立的地址空间, 故进程间不能共享数据, 该进程下的多个线程共享该进程的地址空间, 但线程也有自己独立的堆栈和局部变量. 进程相比线程, 切换也会消耗更多资源
-
锁是防止资源争抢, 但由于Python进程存在GIL锁, 同个进程中同时只有一个线程会执行, 那为什么还有threading.Lock. 因为CPU调度的基本单位是线程, 很有可能A线程读取了某数据后发生了线程切换, B线程修改了该数据, 所以要加锁. 协程是不需要加锁的, 因为协程的切换完全由我们控制
-
孤儿进程
-
每个进程都有一个创建自己的进程, 每个进程的父进程又有自己的父进程, 回溯到1号进程即为init进程, 即所有进程的父亲. 如果子进程的父进程退出, 则子进程就会变为"孤儿进程", init进程会立即收养该进程
-
-
僵尸进程
-
子进程通过父进程fork出来, 子进程退出时, 父进程没有通过wait/waitpid获取子进程信息, 子进程的进程描述符仍保留在操作系统中, 这种就是僵尸进程
-
当子进程退出时, 内核会向父进程发送SIGCHLD信号, 子进程的退出是个异步事件, 子进程可以在父进程运行的任何时刻终止. 子进程退出时, 内核将子进程置为僵尸状态, 这个进程成为僵尸进程, 它只保留最小的一些内核数据结构, 以便父进程查询子进程的退出状态, 父进程通过wait/waitpid查询子进程的退出状态
-
进程调用wait后立即阻塞, 由wait分析是否当前进程的某个子进程已经退出, 如果发现一个已经变成僵尸的子进程, wait会收集这个子进程的信息, 并把它彻底销毁后返回. 如果未发现这样的子进程, wait会一直阻塞
-
-
通信
-
进程间通信方式
-
每个进程的用户地址空间都是独立的, 而内核空间是共享的, 所以进程间通信需要通过内核
-
管道pipe
-
管道的实质是一个内核缓冲区, 一端的进程顺序的将数据写入缓冲区, 另一端的进程则顺序的读出数据. 该缓冲区可以看做是一个循环队列
-
管道中数据传输是单向的, 分为匿名管道(|), 命名管道(mkfifo)
-
缺点是通信效率低, 只能单向
-
-
信号量
-
用于进程间的互斥与同步, 用户对共享内存进行保护
-
-
信号
-
消息队列
-
消息队列是存放在内核中的消息链表
-
缺点是通信不及时, 不适合比较大的数据传输, 且数据存在用户空间至内核空间的拷贝
-
-
共享内存, mmap
-
内存管理采用的是虚拟内存技术, 拿出一块虚拟地址空间映射相同的物理内存上
-
内核专门留出了一块内存区, 可以由需要访问的进程将其映射到自己的私有地址空间. 进程就可以直接读写这一块内存而不需要进行数据的拷贝, 从而大大提高效率
-
-
套接字
-
-
线程间通信方式
-
全局变量
-
锁 (互斥锁, 共享锁)
-
信号量
-
-
线程间同步方式
-
锁 (互斥量)
-
事件
-
信号量
-
-
-
GIL产生的原因是CPython解释器的内存管理是线程不安全的, 为了保证多线程下的数据同步, 就加了一把互斥大锁. CPython每执行100条字节码, 解释器就自动释放GIL锁, 让别的线程有机会执行. 或线程阻塞时也会释放GIL锁
-
线程安全
-
线程安全指的不是线程是否安全, 而是指内存是否安全
-
目前操作系统都是多任务的, 多个进程同时进行, 因为每个进程都只能访问自己的地址空间, 所以进程操作内存不存在问题
-
线程则共用进程的地址空间, 线程A访问堆中的变量后休息了, 之后线程B修改了该变量, 调度再切回线程A执行时, 就会出问题
-
解决方法
-
只读
-
悲观锁/乐观锁
-
-
-
-
协程
-
定义
-
协程可以被叫做微线程, 纤程
-
协程是一种用户态的轻量级线程
-
协程可以在执行期间暂停, 等待外部操作完成后, 再在暂停地方恢复执行. 直接调用协程并不会执行, 而是返回一个协程对象, 协程对象需要被注册到事件循环中, 由事件循环调用
-
相比于进程/线程, 协程不需要上下文的切换, 不需要考虑资源竞争的情况, 故不需要考虑锁与数据在多个线程间的同步. 进程最佳数量为CPU核数, 线程最佳数量需要实际试验, 协程最佳数量非常大
-
协程是单线程的, 由用户决定在哪里交出控制权, 切换到下一个任务. 故我们需要对事件循环/在哪里会有阻塞比较了解
-
操作系统中资源分配的基本单位是进程, CPU调度的基本单位是线程, 操作系统是感受不到协程的
-
协程允许我们写同步代码的逻辑, 却做异步的事情, 避免回调嵌套, 使代码逻辑清晰
-
Python的asyncio库 = 协程 + 事件循环(底层epoll) + task(future)
-
-
事件循环
-
事件循环是在程序中等待, 分发事件的编程结构
-
事件循环的作用是管理所有的事件, 在程序运行中不断循环已注册事件的现状来找到对应的操作, 当事件发生时, 将它们放到预备队列中, 当主线程空闲的时候, 调用相应的事件处理者来处理事件. 简言之, 事件循环的作用是当事件A发生后执行B
-
我们简单认为一个任务有预备, 等待两个状态. 事件循环维护两个任务列表, 一个是预备状态列表, 一个是等待状态列表
-
预备状态: 任务目前处于空闲, 随时可以执行
-
等待状态: 任务目前已经运行, 但在等待外部操作完成, 比如IO
-
-
每次事件循环都从预备状态列表中选取一个任务执行, 一直到这个任务把控制权交换事件循环为止. 当任务将控制权交还事件循环, 事件循环会检查该任务, 并将其加入相应队里中. 事件循环接着会遍历等待状态列表, 将其中完成的再加入预备状态列表中. 重复当前步骤
-
-
任务
-
task用于包装协程, 将协程赋予状态
-
-
何时使用协程
-
IO密集型, IO较多, IO较慢时, 使用协程
-
IO密集型, IO较少, IO较快时, 使用线程即可
-
CPU密集型, 使用进程
-
并发与并行
-
Python中只有进程能做到并行
-
Python的线程与协程只是并发
-
-
-
async/await
-
用async修饰的都是异步函数, await只能用在异步函数中
-
对基于生成器的协程的支持将在Python3.10中移除, 故不建议使用. 基于生成器的协程是早期的异步实现方式, 出现在Python3.5中的async/await语法之前, 使用yield from表达式等待Future或其他协程. 基于生成器的协程应该用@asyncio.coroutine来修饰
-
async/await产生的原因是为了区分生成器和协程, 用yield/yield from使生成器和协程两概念搞混了, 故再引入@asyncio.coroutine来区分生成器和协程. async/await被称为原生协程, yield from/@asyncio.coroutine被称为基于生成器的协程.
-
# 基于生成器的协程 @asyncio.coroutine def old_style_coroutine(): yield from asyncio.sleep(1) # 原生协程 async def main(): await old_style_coroutine()
-
同步函数调用协程, 可使用"协程.send(None)"
-
-
常用函数
-
asyncio.run(协程)
-
asyncio.create_task(协程): 将协程转为任务, 并将该任务加入事件循环的预备列表中
-
await 可等待对象 (参考自https://blog.youkuaiyun.com/qq_29785317/article/details/97054589)
-
可等待对象: 协程/任务/future. 如果await后面是函数, 就是同步调用, 会等待调用结束. 实现了_ _ await _ _方法的就是可等待对象
-
如果是协程, 先将该协程包装成任务, 并将该任务加入事件循环中, 并挂起当前任务, 即将控制权交回事件循环
-
如果是任务, 挂起当前任务, 即将控制权交回事件循环
-
-
yield from
-
预激生成器
-
调用者直接和内部生成器通信
-
-
-
「操作系统」进程 线程 协程
最新推荐文章于 2023-04-17 22:17:32 发布