Go中实现阻塞的方式
qingheluo2025-06-30清河洛13
我们知道Go的一大优势就是天然支持并发,但是在使用并发时,如果一旦主协程退出,那么所有的子协程也会强制退出,这显然不是我们想要的结果所以阻塞主协程直到等待所有子协程运行完毕后再结束主协程是编写并发程序必须要做的死锁:在所有子协程运行完毕后如果主协程没有解除阻塞的操作逻辑,称之为死锁,此时会panic,所以在阻塞主协程时一定要注意死锁有条件的解除阻塞time.Sleep这也是在学习Go的时候最早接触的用于阻塞协程的方式,简单粗暴func main(){
go func(){
// ...
}()
time.Sleep(time.Second)
}这...
我们知道Go的一大优势就是天然支持并发,但是在使用并发时,如果一旦主协程退出,那么所有的子协程也会强制退出,这显然不是我们想要的结果
所以阻塞主协程直到等待所有子协程运行完毕后再结束主协程是编写并发程序必须要做的
死锁:在所有子协程运行完毕后如果主协程没有解除阻塞的操作逻辑,称之为死锁,此时会panic,所以在阻塞主协程时一定要注意死锁
有条件的解除阻塞
time.Sleep
这也是在学习Go的时候最早接触的用于阻塞协程的方式,简单粗暴
func main(){
go func(){
// ...
}()
time.Sleep(time.Second)
}
这种方式主协程什么时候解除阻塞与子协程完全无关,这就导致无法保证子协程是否已经运行完毕,一般情况下不会使用这种方式
sync.WaitGroup
在运行子协程之前通过Add方法向计数器添加子协程数量,子协程运行完毕后运行Done()来减少这个计数器
然后主协程通过运行Wait()方法来阻塞的等待计数为0后解除阻塞
func main(){
wg := sync.WaitGroup()
wg.Add(2)
go func(){
defer wg.Done()
// ...
}()
wg.Wait()
}
channel
通过利用当读取一个无缓存的通道时会阻塞直到有协程向该通道中写入这一特性
func main(){
ch := make(chan struct{})
go func(){
// ...
ch <- struct{}{}
}()
<- ch
}
sync.Mutex
互斥锁有一个特性:一个互斥锁被一个协程加了锁之后其他协程对该互斥锁进行加锁操作都会阻塞直到该互斥锁被解锁
利用该特性
1、在子协程运行之前,在主协程使用mutex.Lock()进行加锁
2、子协程在运行结束后运行mutex.Unlock()进行解锁
3、主协程最后使用mutex.Lock()再次进行加锁
这时,只要子协程没有结束并运行解锁,那么主协程第二次的加锁操作就会阻塞,直到子协程进行了解锁操作
func main(){
var mutex sync.Mutex
mutex.Lock()
go func(){
defer mutex.Unlock()
// ...
}()
mutex.Lock()
}
永不解除的阻塞
for{} (没有结束条件)
for{}在for后面并没有写明循环结束的条件,在主协程的最后使用创造一个不会结束的死循环
func main(){
go func(){
// ...
}()
for {}
}
虽然for的死循环什么都没做,但是仍然会频繁的占用CPU时间,所以一般在循环体中会使用一些延时操作以避免无限制的 CPU 占用
select{} (不带case)
select循环会随机执行一个case块,当没有case块时,会直接阻塞
func main(){
go func(){
// ...
}()
select {}
}
这种方式是真正的阻塞,而不是for{}那样的无限循环,所以不会占用CPU时间,但是相比for{}语句,无法有条件的退出循环