并发编程概要

本文将为你梳理现代并发编程的重要知识点。注意这只是梳理,更具体的概念与应用案例请自行查阅。

多线程(OS线程)

进程是一个程序的执行过程、操作系统资源分配的最小单位。单程序仅靠进程无法充分利用CPU的多核性能,能实现并发、却无法真正并行,因此出现了线程的概念。一个进程可以包含多个线程,线程是CPU调度的最小单位。

OS线程(操作系统线程)是最常见的并发实现方式,大部分现代编程语言都会提供相关 API(如 C++、C#、Java、Rust 中的 thread )。

多线程编程的两个重要问题是线程同步资源共享

资源共享(互斥问题)

同一进程的线程间可以通过共享内存进行通信,因此资源共享实际需要解决的是互斥访问的问题。实际应用中主要涉及以下内容:

  • 互斥锁(Mutex):最常见的锁,等待时会挂起线程
  • 自旋锁(Spin):忙等待的锁
    • 适合极短时间等待的场景,此时挂起恢复线程的耗时可能比等待更久
  • 读写锁(RwMutex):允许并发读、独占写
    • 适合数据库访问等读请求显著多于写请求的场景
  • 原子变量(Atomic):通过底层 CPU 指令保证互斥的读写,比锁更快
  • 信道(channel):一种队列的互斥封装,一端写入、另一端读取
    • 读写操作内含互斥封装
    • 适合生产者消费者场景

线程同步

按照 channel 的一般实现,在读空队列或写满队列时允许挂起线程直到操作成功,因此可用于线程同步。

此外还有下列三种常见同步原语:

  • 条件变量(condition-variable):获取锁后检查条件是否成立,如果条件不成立则释放锁并等待唤醒。唤醒后获取锁并重复上面的检查,如果条件成立则可进行后续操作(此时持有锁、不释放)。
  • 信号量(semaphore):
    • 二进制信号量:0 表示未激活,1 表示已经激活。前置任务完成后将信号量置 1,后续任务等待信号量变成 1 后再开始(同时重置信号量回 0 )。
    • 时间线(计数)信号量:保持单调递增,不同的任务需要等待不同的时间点。当前任务完成后增加信号量的值、从而开始下一个任务。
  • 屏障(barrier):如同一个屏障分隔前后两批任务,所有前置任务都完成才会释放屏障,后续任务才能开始。

并发模型

OS 线程是最常见的并发模型,但OS线程的内存占用和调度开销太大,不适合直接用于网络服务等I/O场景(高并发、久等待)。因此存在更多的并发模型用于解决此类问题:

  • IO多路复用(I/O Multiplexing):
    • 每个 OS 线程处理多条 IO 请求。
    • 可以使用轮询的方式依次处理每个IO任务,若IO还在进行中则直接跳过(不等待)。
    • 借助操作系统 API (如Linux的epoll)实现响应式的IO处理,避免轮询开销。
    • 常见于 C 风格的网络编程,如 Nginx 和 Redis 的实现。
    • 注意区分 IO模型并发模型
  • 协程(Coroutine):
    • 需编程语言支持。
    • 指可挂起和恢复的函数,函数将被封装成某种状态机对象。
    • 使用一块堆内存存放函数的执行状态与局部变量,因此可按需挂起和恢复。
    • 参考 C++20 的 Coroutine
  • 用户级线程(User-level Thread):
    • 需编程语言支持。
    • 实际是程序实现一种的“模拟线程”,同样是一种状态机对象。
    • 因为是程序级实现,其调度更加廉价,内存占用也可以设置的更低。
    • 实际的并行依赖OS线程,因此模拟线程与OS线程是M:N的对应关系。
    • 由程序级的“调度器(运行时)”管理状态机的挂起与执行。
    • 编程风格与OS线程类似。
    • 参考 Go 的 Goroutine
  • 异步(Asynchronous):
    • 需编程语言支持。
    • 使用 async 关键字将函数声明为状态机。
    • 函数调用返回状态机对象 promise/future,内含函数执行状态与局部变量。
    • 使用 await 关键字执行目标状态机并挂起当前状态机(直到目标完成)。
    • 由程序级的“调度器(运行时)”管理状态机的挂起与执行。
    • 通过类似同步的代码实现异步的效果。
    • 参考 JS、C#、Rust 的 async/await

可以注意到,协程、异步与用户级线程有很多相似之处。协程像是最低级的状态机封装,而后两者增加了调度器的概念,并选择了两种不同的编程风格。

有人会将 Go 的 Goroutine 也称之为“协程”,迷枵认为这是不准确的。

并发模型的选择很大程度上取决于使用的编程语言,现代编程语言往往都会提供一套针对 IO 场景的解决方案,而 C 语言等早期语言则不得不使用 IO 多路复用的方案。

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇