一、相关概念
串行
同一时刻,一个任务接着一个任务的执行
并行
同一时刻,执行多个任务
并发
同一时刻内分多个时间片执行多个任务,宏观上像“同一时刻执行多个任务”
进程
是应用程序运行的抽象,比如启动一个QQ,就是启动一个进程。进程包含两部分
- 静态部分:程序运行需要的代码和数据
- 动态部分:程序运行期间的状态
线程
更轻量级运行的抽象,是进程的一个执行单元,是cpu调度的基本单位。只包含动态部分
“线程由谁来管理”分类:
- 用户级线程:线程的创建、调度、管理全由应用程序自己通过 “线程库”(比如用户自己写的工具库)完成,操作系统内核完全 “不知道” 这些线程的存在
- 内核级线程:线程的创建、调度、管理全由操作系统内核负责,内核直接操控这些线程的运行
协程
用户态下的轻量级线程,上下文切换不需要内核参与。go语言的协程叫goroutine
二、实现并发程序
在go语言中,使用go关键字就可以开启一个协程。调度器先将goroutine映射到 OS 线程,再由CPU调度,从而实现并发执行,实现高并发程序
三、协程间的通信
Channel,引用类型,声明:var 变量名 chan 数据类型,初始化make (chan 元素类型 [缓冲大小] ) (缓冲可选)
1. 有无缓冲
没有缓冲,队列大小为0,所以必须要有一个接收者,不然会造成阻塞形成死锁;要不然就是设置缓冲
2. 从通道取值
for循环;for range循环
select
在 Go 语言中,select 是用于多路复用通道操作的关键字,它可以同时监听多个通道的发送或接收操作,哪个操作先就绪就执行哪个,若都不就绪则会阻塞(或执行default分支)
func main() {
ch := make(chan int) //创建int类型无缓冲通道ch
/* go匿名协程 */ go func() {
time.Sleep(3 * time.Second) //休眠3s
ch <- 1 //向通道ch发送数据1(无缓冲通道需等待接收方就绪)
}()
select { //多路复用:同时监听多个通道操作,谁先就绪执行谁
case data, ok := <-ch: //尝试从通道ch接收数据,data存接收的值,ok标记通道状态
if ok { //ok为true:通道未关闭且接收到数据
fmt.Println(data) //打印data
} else {
fmt.Println("channel closed") //ok为false:通道已关闭且无剩余数据
}
case <-time.After(2 * time.Second): //创建定时器通道,2秒后自动发送一个时间值(触发超时)
fmt.Println("timeout") //打印timeout
}
}
3. 锁🔒
3.1 互斥锁
同一时间只允许一个协程访问共享资源
package main
import (
"fmt"
"sync" "time")
var (
mu sync.Mutex // 互斥锁
count int // 共享变量
)
func main() {
// 启动5个协程并发修改count
for i := 0; i < 5; i++ {
go func(id int) {
mu.Lock() // 加锁:独占共享资源
count++ // 临界区:修改共享变量(必须加锁保护)
fmt.Printf("协程%d:count=%d\n", id, count) // 临界区内打印(安全)
mu.Unlock() // 解锁:释放资源
}(i)
}
time.Sleep(100 * time.Millisecond) // 等待所有协程执行完
fmt.Println("最终count:", count) // 主协程打印最终结果(安全,无竞争)
}
3.2 读写锁
读操作可多个协程同时进行,写操作会独占资源(读与写、写与写互斥)。适用读多写少的场景,提高性能
package main
import (
"fmt"
"sync"
"time"
)
var (
rw sync.RWMutex // 定义读写锁
data = "初始数据" // 共享数据
)
func main() {
// 启动5个读协程(并发执行)
for i := 0; i < 5; i++ {
go func(readID int) {
rw.RLock() // 加读锁:允许多个读协程同时加锁
fmt.Printf("读协程%d:%s\n", readID, data)
time.Sleep(500 * time.Millisecond) // 模拟读耗时
rw.RUnlock() // 解读锁
}(i)
}
// 启动1个写协程(独占执行)
go func() {
time.Sleep(100 * time.Millisecond) // 等读协程先启动
rw.Lock() // 加写锁:阻塞所有读/写操作
data = "更新后的数据" // 写操作(共享资源)
fmt.Println("写协程:已更新数据")
rw.Unlock() // 解写锁
}()
time.Sleep(2 * time.Second) // 等待所有协程执行完(简化写法)
}