1.并发
并发模型一般有四种,分别是多进程编程、多线程编程、非阻塞异步I/O编程和基于协调的编程
多进程编程
多进程编程利用操作系统的进程管理能力,每个进程拥有独立的内存空间和系统资源。进程间通信(IPC)需要通过特定机制(如管道、共享内存、消息队列)实现。适用于CPU密集型任务,能充分利用多核优势,但创建和切换开销较大。
多线程编程
多线程编程在单个进程内创建多个执行流,共享同一内存空间。线程切换成本低于进程,适合I/O密集型任务。需注意线程安全问题(如竞态条件、死锁)。全局解释器锁(GIL)在部分语言(如Python)中会限制多线程的并行效率。
非阻塞异步I/O编程
通过事件循环和回调机制处理并发,单线程即可管理大量I/O操作。当I/O未就绪时立即返回,避免线程阻塞。典型实现包括Node.js的EventEmitter、Python的asyncio。适合高并发网络服务,但对CPU密集型任务效果有限。
基于协调的编程
以协程(Coroutine)为核心,通过用户态调度实现协作式多任务。协程主动让出执行权而非被系统抢占,切换开销极低。常见于Go语言的goroutine、Python的generator。需开发者显式处理协程调度,适合高吞吐量场景。
2.并发与并行
并发是指同一时刻只能有一条指令执行,但多个进程指令被快速地轮换执行,达到宏观上有多个进程同时执行的效果

并行是指在同一时刻,有多条指令在多个处理器上同时执行

并发与并行的区别:
记住一句核心口诀 :并发:交替执行(同一 CPU,来回切换) 并行:同时执行(多个 CPU,一起干活)
通俗大白话解释就是
- 并发(Concurrent)
一个 CPU 核心,同一时间只能做一件事 快速切换任务:吃饭→看书→吃饭→看书看起来同时在做,实际同一时刻不是同时
特点:交替、快速切换、伪同时、单核可用
- 并行(Parallel)
多个 CPU 核心,同一时刻真正一起做事一个核心吃饭,一个核心看书真正同时进行
特点:同时、一起执行、真同时、必须多核
二、对比表格
| 对比维度 | 并发 Concurrent | 并行 Parallel |
|---|---|---|
| CPU 数量 | 单核 / 多核都可以 | 必须多核 |
| 同一时刻状态 | 只能执行 1 个任务 | 同时执行多个任务 |
| 执行方式 | 快速交替切换 | 同时独立运行 |
| 本质 | 逻辑上同时 | 物理上同时 |
| 生活例子 | 一个人交替洗碗、扫地 | 两个人同时洗碗、扫地 |
3.goroutine协调创建
goroutine是Go语言中的轻量级线程实现,有Go运行时(runtime)进行管理,它会智能地将goroutine中的任务合理地分配给每个CPU
(1)普通函数创建 goroutine
go funcName(paramlist)
go:是创建goroutine使用的关键字
funcName:是函数名
paramlist:函数参数
示例代码:
package main
import (
"fmt"
"time"
)
// 普通函数
func printMessage(msg string) {
fmt.Println(msg)
}
func main() {
// 使用普通函数创建goroutine
go printMessage("Hello from goroutine")
// 主goroutine继续执行
fmt.Println("Hello from main")
// 等待足够时间确保goroutine执行
time.Sleep(1 * time.Second)
}
(2)使用匿名函数创建 goroutine
go func(paramlist){
执行代码
}(paramlist2)
go:是创建goroutine使用的关键字
func:是创建匿名函数使用的关键字
paramlist:是匿名函数使用的形参
paramlist2:是匿名函数使用的实参
示例代码:
package main
import (
"fmt"
"time"
)
func main() {
// 使用匿名函数创建goroutine
go func(msg string) {
fmt.Println(msg)
}("Hello from anonymous goroutine")
// 主goroutine继续执行
fmt.Println("Hello from main")
// 等待足够时间确保goroutine执行
time.Sleep(1 * time.Second)
}
注意事项
两个示例中都使用了time.Sleep来确保goroutine有足够时间执行。在实际应用中,通常使用更可靠的同步机制如sync.WaitGroup或通道(channel)来协调goroutine的执行。
4.channel通信机制
channel(通道)是Go语言中的一个核心类型,可以把他看成一个管道,并发核心单元通过它可以发送或者接受函数,实现通信功能
(1)声明通道类型
var chanName chan chanType
chanName:表示保存通道的变量,声明后默认为nil,需要通过make()函数创建后才能使用
chanType:表示通道内的数据类型
(2)创建通道(无缓冲通道)
chanName := make(chan chanType)
(3)使用通道发送数据
chanName <- chanValue
chanValue:是变量、常量、表达式、函数返回值等
(4)使用通道阻塞接收数据
data := <-ch
上面的语法执行过程中会出现通道阻塞,知道接收到数据并赋值给变量data
(5)使用通道非阻塞接收数据
data,ok := <-ch
(6)忽略接收到的所以数据
<-ch
上面的语法在执行过程中会出现通道阻塞,直到接收到数据,但接收到的数据会忽略
(7)创建通道(有缓冲)
chanName := make(chan chanType,cacheSize)
在以上的语法中,变量chanName表示通过make()函数创建好的通道实例,参数chanType表示通道发送和接收的数据类型,参数cacheSize表示通道最多可以保存的元素数量
(8)无缓冲通道有缓冲通道的特点和阻塞条件
无缓冲的通道是指在接收信息前没有能力保存任何值的通道。这种类型的通道要求执行发送的goroutine和执行接收的goroutine同时准备好,才能完成发送和接收的操作。因为无缓冲通道的发送和接收行为时同步的,两者任意一个操作都无法离开另一方的操作。因此若两个goroutine没有同时准备好,会导致先执行发送或接收操作的goroutine进入阻塞等待状态
使用无缓冲通道实现同步通信的示例代码
以下代码演示了如何使用无缓冲通道在Go协程之间进行同步通信。无缓冲通道要求发送和接收操作同时准备好才能成功通信,否则会阻塞。
package main
import (
"fmt"
"time"
)
func worker(done chan bool) {
fmt.Println("working...")
time.Sleep(time.Second)
fmt.Println("done")
done <- true
}
func main() {
done := make(chan bool)
go worker(done)
<-done
}
有缓冲的通道是一个有限大小的存储空间形成的带有缓冲的通道,信息被接收前能存储一个或多个值,不强制要求goroutine之间必须同时完成发送和接收操作。因此只有在通道中没有要接收的值时,接收操作才会阻塞;只有在通道没有可用缓冲区容纳被发送的值时,发送动作才会阻塞
使用缓冲通道实现异步通信示例代码
缓冲通道允许在通道容量未满时不阻塞发送操作。这段代码展示了缓冲通道的用法
package main
import "fmt"
func main() {
messages := make(chan string, 2)
messages <- "buffered"
messages <- "channel"
fmt.Println(<-messages)
fmt.Println(<-messages)
}