「操作系统」进程 线程 协程

本文详细介绍了进程与线程的区别,包括它们的定义、组成和通信方式,强调了进程是资源分配的最小单位,线程是CPU调度的最小单位。接着探讨了线程与协程,指出线程间的通信和同步机制,以及Python中的GIL锁。重点讲解了协程的概念,如何通过事件循环和任务来管理协程,并讨论了何时使用协程以及async/await语法。最后,提到了进程间通信的各种方法,如管道、信号量、消息队列和共享内存。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

  • 作业与进程的区别

    1. 前者是由用户提交,后者是由系统自动生成;前者以用户任务为单位,后者是操作系统控制的单位.

    2. 作业 = 程序 + 数据 + 说明书, 进程 = 程序 + 数据 + PCB

    3. 一个作业可以分为多个进程来完成

  • 进程与线程

    1. 进程是资源分配的最小单位, 线程是CPU调度的最小单位

    2. 一个进程下可包含1至多个线程

    3. 一个进程有独立的地址空间, 故进程间不能共享数据, 该进程下的多个线程共享该进程的地址空间, 但线程也有自己独立的堆栈和局部变量. 进程相比线程, 切换也会消耗更多资源

    4. 锁是防止资源争抢, 但由于Python进程存在GIL锁, 同个进程中同时只有一个线程会执行, 那为什么还有threading.Lock. 因为CPU调度的基本单位是线程, 很有可能A线程读取了某数据后发生了线程切换, B线程修改了该数据, 所以要加锁. 协程是不需要加锁的, 因为协程的切换完全由我们控制

    5. 孤儿进程

      1. 每个进程都有一个创建自己的进程, 每个进程的父进程又有自己的父进程, 回溯到1号进程即为init进程, 即所有进程的父亲. 如果子进程的父进程退出, 则子进程就会变为"孤儿进程", init进程会立即收养该进程

    6. 僵尸进程

      1. 子进程通过父进程fork出来, 子进程退出时, 父进程没有通过wait/waitpid获取子进程信息, 子进程的进程描述符仍保留在操作系统中, 这种就是僵尸进程

      2. 当子进程退出时, 内核会向父进程发送SIGCHLD信号, 子进程的退出是个异步事件, 子进程可以在父进程运行的任何时刻终止. 子进程退出时, 内核将子进程置为僵尸状态, 这个进程成为僵尸进程, 它只保留最小的一些内核数据结构, 以便父进程查询子进程的退出状态, 父进程通过wait/waitpid查询子进程的退出状态

      3. 进程调用wait后立即阻塞, 由wait分析是否当前进程的某个子进程已经退出, 如果发现一个已经变成僵尸的子进程, wait会收集这个子进程的信息, 并把它彻底销毁后返回. 如果未发现这样的子进程, wait会一直阻塞

    7. 通信

      1. 进程间通信方式

        1. 每个进程的用户地址空间都是独立的, 而内核空间是共享的, 所以进程间通信需要通过内核

        2. 管道pipe

          1. 管道的实质是一个内核缓冲区, 一端的进程顺序的将数据写入缓冲区, 另一端的进程则顺序的读出数据. 该缓冲区可以看做是一个循环队列

          2. 管道中数据传输是单向的, 分为匿名管道(|), 命名管道(mkfifo)

          3. 缺点是通信效率低, 只能单向

        3. 信号量

          1. 用于进程间的互斥与同步, 用户对共享内存进行保护

        4. 信号

        5. 消息队列

          1. 消息队列是存放在内核中的消息链表

          2. 缺点是通信不及时, 不适合比较大的数据传输, 且数据存在用户空间至内核空间的拷贝

        6. 共享内存, mmap

          1. 内存管理采用的是虚拟内存技术, 拿出一块虚拟地址空间映射相同的物理内存上

          2. 内核专门留出了一块内存区, 可以由需要访问的进程将其映射到自己的私有地址空间. 进程就可以直接读写这一块内存而不需要进行数据的拷贝, 从而大大提高效率

        7. 套接字

      2. 线程间通信方式

        1. 全局变量

        2. 锁 (互斥锁, 共享锁)

        3. 信号量

      3. 线程间同步方式

        1. 锁 (互斥量)

        2. 事件

        3. 信号量

    8. GIL产生的原因是CPython解释器的内存管理是线程不安全的, 为了保证多线程下的数据同步, 就加了一把互斥大锁. CPython每执行100条字节码, 解释器就自动释放GIL锁, 让别的线程有机会执行. 或线程阻塞时也会释放GIL锁

    9. 线程安全

      1. 线程安全指的不是线程是否安全, 而是指内存是否安全

      2. 目前操作系统都是多任务的, 多个进程同时进行, 因为每个进程都只能访问自己的地址空间, 所以进程操作内存不存在问题

      3. 线程则共用进程的地址空间, 线程A访问堆中的变量后休息了, 之后线程B修改了该变量, 调度再切回线程A执行时, 就会出问题

      4. 解决方法

        1. 只读

        2. 悲观锁/乐观锁

  • 协程

    1. 定义

      1. 协程可以被叫做微线程, 纤程

      2. 协程是一种用户态的轻量级线程

      3. 协程可以在执行期间暂停, 等待外部操作完成后, 再在暂停地方恢复执行. 直接调用协程并不会执行, 而是返回一个协程对象, 协程对象需要被注册到事件循环中, 由事件循环调用

      4. 相比于进程/线程, 协程不需要上下文的切换, 不需要考虑资源竞争的情况, 故不需要考虑锁与数据在多个线程间的同步. 进程最佳数量为CPU核数, 线程最佳数量需要实际试验, 协程最佳数量非常大

      5. 协程是单线程的, 由用户决定在哪里交出控制权, 切换到下一个任务. 故我们需要对事件循环/在哪里会有阻塞比较了解

      6. 操作系统中资源分配的基本单位是进程, CPU调度的基本单位是线程, 操作系统是感受不到协程的

      7. 协程允许我们写同步代码的逻辑, 却做异步的事情, 避免回调嵌套, 使代码逻辑清晰

      8. Python的asyncio库 = 协程 + 事件循环(底层epoll) + task(future)

    2. 事件循环

      1. 事件循环是在程序中等待, 分发事件的编程结构

      2. 事件循环的作用是管理所有的事件, 在程序运行中不断循环已注册事件的现状来找到对应的操作, 当事件发生时, 将它们放到预备队列中, 当主线程空闲的时候, 调用相应的事件处理者来处理事件. 简言之, 事件循环的作用是当事件A发生后执行B

      3. 我们简单认为一个任务有预备, 等待两个状态. 事件循环维护两个任务列表, 一个是预备状态列表, 一个是等待状态列表

        1. 预备状态: 任务目前处于空闲, 随时可以执行

        2. 等待状态: 任务目前已经运行, 但在等待外部操作完成, 比如IO

      4. 每次事件循环都从预备状态列表中选取一个任务执行, 一直到这个任务把控制权交换事件循环为止. 当任务将控制权交还事件循环, 事件循环会检查该任务, 并将其加入相应队里中. 事件循环接着会遍历等待状态列表, 将其中完成的再加入预备状态列表中. 重复当前步骤

    3. 任务

      1. task用于包装协程, 将协程赋予状态

    4. 何时使用协程

      1. IO密集型, IO较多, IO较慢时, 使用协程

      2. IO密集型, IO较少, IO较快时, 使用线程即可

      3. CPU密集型, 使用进程

      4. 并发与并行

        1. Python中只有进程能做到并行

        2. Python的线程与协程只是并发

    5. async/await

      1. 用async修饰的都是异步函数, await只能用在异步函数中

      2. 对基于生成器的协程的支持将在Python3.10中移除, 故不建议使用. 基于生成器的协程是早期的异步实现方式, 出现在Python3.5中的async/await语法之前, 使用yield from表达式等待Future或其他协程. 基于生成器的协程应该用@asyncio.coroutine来修饰

      3. async/await产生的原因是为了区分生成器和协程, 用yield/yield from使生成器和协程两概念搞混了, 故再引入@asyncio.coroutine来区分生成器和协程. async/await被称为原生协程, yield from/@asyncio.coroutine被称为基于生成器的协程.

      4. # 基于生成器的协程
        @asyncio.coroutine
        def old_style_coroutine():
            yield from asyncio.sleep(1)
        
        # 原生协程
        async def main():
        ​    await old_style_coroutine()
      5. 同步函数调用协程, 可使用"协程.send(None)"

    6. 常用函数

      1. asyncio.run(协程)

      2. asyncio.create_task(协程): 将协程转为任务, 并将该任务加入事件循环的预备列表中

      3. await 可等待对象 (参考自https://blog.youkuaiyun.com/qq_29785317/article/details/97054589)

        1. 可等待对象: 协程/任务/future. 如果await后面是函数, 就是同步调用, 会等待调用结束. 实现了_ _ await _ _方法的就是可等待对象

        2. 如果是协程, 先将该协程包装成任务, 并将该任务加入事件循环中, 并挂起当前任务, 即将控制权交回事件循环

        3. 如果是任务, 挂起当前任务, 即将控制权交回事件循环

      4. yield from

        1. 预激生成器

        2. 调用者直接和内部生成器通信

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值