概念
并发/并行
- 并发指的是多线程序在一个CPU同时相应后,安排不同时间运行
- 并行是多个CPU同时运行多个线程
协程/线程
- 协程是用户态,轻量级的线程,栈KB级别
- 线程是内核态,线程跑多个协程,栈MB级别
阻塞/非阻塞
- 阻塞是数据未返回时会等待
- 非阻塞是不需要等待数据返回
同步/异步
- 同步是发起调用时,等待返回结果
- 异步是发起调用后,调用结束,有结果之后才会通知 举个例子就是,去医院看抽血,在那里抽血完不做别的事情等待结果,就是同步,如果抽血完回去做别的事情,等待医院通知结果,再去拿结果,就是异步
通过通道共享内存
![[Pasted image 20230725154143.png|600]]
sync锁
概念
互斥锁
- 多个任务需要处理同一个资源,但是同一时间内只有一个能使用该资源
- 通过lock(上锁)unlock(解锁)方式控制,保证同一时间内只有一个能使用该资源
读写锁
- 多个线程允许读,但是只有一个线程允许写入
- 同一时间多个线程都可以申请读锁,但是只有一个线程能申请读锁(申请到写锁之后,读锁也需要等待写入完成)
- 用于频繁读取但是不频繁写入的场景
包引入
import "sync"
sync.Mutex 互斥锁
mutex := &sync.Mutex{}
mutex.Lock() // 上锁
// 操作
mutex.Unlock() // 解锁
sync.RWMutex 读写锁
mutex := &sync.RWMutex{}
mutex.Lock() // 加写锁
// 操作
mutex.Unlock() // 解写锁
mutex.RLock() // 加读锁
// 操作
mutex.RUnlock() // 解读锁
sync.WaitGroup
- WaitGroup 是一个计数器,n个并发任务时计数n,当n=0时结束
var wg sync.WaitGroup
func hello(){
// 表示该协程以及结束,计数器 -1
defer wg.Done()
fmt.Println("hello world")
}
func main(){
for i = 0; i < 2; i++ {
// 计数器加1
wg.Add(1)
// 两个协程执行hello()
go hello()
}
// 阻塞直到变0
wg.Wait()
}
sync.Once
- 应用于某些高并发场景但是某些只执行一次的操作
- 比如只配置一次文件,只关闭一次通道
var loadonce sync.Once
func handle(){
code
}
// 函数只执行一次
loadonce.Do(handle())
sync.Map
- go内置的map类型在并发条件下并不是安全的
var m sync.Map
func main(){
// 写入
m.Store("name", "aaa")
// 读取
name,data := m.Load("name")
// 删除
m.Delete("name")
// 读取或写入(如果存在则读出原值,没有则写入)
m.LoadOrStore("name": "bbb")
// 遍历
m.Range(func(key, value interface{}) bool {
fmt.Println(key, value)
return true
})
}
atomic原子操作
使用sync应该保护一段逻辑,而不是一段变量,如果只是涉及到某个int或string类型上锁,应该使用原子操作
import "atomic"
var a int64
// 读取操作
atomic.LoadInt64(x)
// 写入操作
atomic.StoreInt64(&x)
//
创建协程goroutines
- 正常情况下,两个函数执行是有先后顺序的
- 但是创建协程之后两个函数的调用可能发生在同一时间
// 在go关键字后面添加函数即可创建channel
go func(){
code
}(data)
select选择器
多个chan等待时,如果直接使用多个 s := <-ch 性能会很差,所以使用select
ch1 := make(chan int)
ch2 := make(chan int)
select {
case s1 := <- ch1: // ch1获取到数据时执行
code
case s2 := <- ch2: // ch2获取到数据时执行
code
}
context上下文
channel信道
概念
- channel相当于队列,遵循先进先出原则
- 相当于一个线程(go的线程是并发的)
- 并发:同时响应,交替执行
- 并行:同时响应,同时执行
- 阻塞:在结果返回前,该线程被挂起,直到得到结果
- 非阻塞:如果不能立即得到结果,返回错误信息(需要定时轮询查看状态)
初始化
- 不带缓冲的通道,进出会阻塞(信道无法储存数据,需要)
- 带缓冲的通道,进一次长度+1,出一次长度-1
// 不带缓冲的channel 选择你需要进行
ch := make(chan type)
// 带11个缓冲的(0-10)channel 表示队列的长度
// 当队列里面数据满了之后,再进入就会出现阻塞
ch := make(chan string,10)
// 只读通道 只写通道
ch := make(<-chan string) ch := make(chan<-string)
读写操作
ch := make(chan int)
// 写入ch
ch <- 2
// 读取ch 保存到x中
x := <-ch
信道长度和容量
ch := make(chan int,10)
// 查看信道可容纳的数据
cap(ch)
// 查看信道当前的长度
len(ch)