1.sync.Mutex
1.1 使用
通过mutex.Lock() 和 mutex.Unlock()来控制代码执行顺序。注意如果锁没有处于上锁状态,执行unlock会报错。"fatal error: sync: unlock of unlocked mutex"
1.2 代码块如下
func main() {
var mutex sync.Mutex
mutex.Lock()
go func() {
time.Sleep(1 * time.Second)
fmt.Println("先打印1")
mutex.Unlock()
}()
mutex.Lock()
fmt.Println("后打印2")
time.Sleep(2 * time.Second)
}
1.3 执行效果如下
2.无缓冲channel通道
2.1 使用
首先创建一个无缓冲的通道,done := make(chan int),通过 <-chan 和 chan<-1将数据写入和读取出来。
无缓冲channel如果之前放入了一个值,这个管道里的值没有读取出来使用的话,另一个协程再放入一个值的时候会阻塞。
2.2 代码块如下
func main() {
done := make(chan int)
go func() {
time.Sleep(1 * time.Second)
fmt.Println("先打印1")
done <- 1
}()
<-done
fmt.Println("后打印2")
time.Sleep(2 * time.Second)
}
2.3 执行效果如下
3. 有缓存channel通道
3.1 使用
和无缓冲channel类似,只不过make函数后面加上了一个缓存大小,不写的时候就是无缓存相当于make(chan int) 等价于 make(chan int,0)。
当管道缓冲的时候,管道里的值没有读取出来使用,其他协程也可以放入并且不会有阻塞,除非当缓冲数量达到管道缓冲容量最大值的时候,也会阻塞。
3.2 代码块如下
func main() {
done := make(chan int, 2)
go func() {
time.Sleep(1 * time.Second)
fmt.Println("先打印1")
done <- 1
done <- 1
done <- 1
fmt.Println("channel还可以继续放入")
}()
<-done
fmt.Println("后打印2")
time.Sleep(8 * time.Second)
}
3.3 执行效果如下
4.sync.WaitGroup
4.1 使用
wg.Add(10)先设置要计数的任务数,各个协程执行完后调用 wg.Done(),在所有任务执行完后,通过wg.Wait()来判断所有任务是否以及完毕,完毕后继续后续代码块执行。
4.2 代码块如下
func main() {
var wg sync.WaitGroup
wg.Add(10)
t1 := time.Now()
for i := 0; i < 10; i++ {
go func() {
random := rand.Intn(4) + 1
time.Sleep(time.Duration(random) * time.Second)
fmt.Printf("这个循环等待了%d秒\n", random)
wg.Done()
}()
}
wg.Wait()
t2 := time.Now()
fmt.Println("执行完毕,用时:", (t2.Second() - t1.Second()))
}
4.3 执行效果如下
5. 基于select实现超时判断退出
5.1 使用
当select有多个分支的时候,会随机选择一个可用的分支。如果没有可用的分支,就用default分支。如果没有default 则会阻塞。
例如有2个分支,一个分支是通过canel管道来执行一段逻辑,然后触发return 退出。另一个分支是用来作为 “如果1秒内还没有收到canel管道的数据写入,则直接退出” 。
需要注意的是,如果select监听的管道被close了,也会触发一个case的执行。(此时如果接受管道的数据,得到的是:bool就是false, int就是0,字符串就是""空字符串,取得对应类型的默认值)
5.2 代码块如下
func main() {
canel := make(chan bool)
go func() {
// 执行正常想要执行的逻辑
time.Sleep(3 * time.Second)
close(canel)
}()
select {
case x := <-canel:
time.Sleep(time.Second)
fmt.Println("通过执行canel2 ", x)
return
case <-time.After(time.Second):
fmt.Println("执行超时退出")
return
}
}
5.3 执行效果如下
6. context包
6.1 使用
context是专门用来简化单个请求的多个groutine之间于请求域的数据、超时和退出等操作。
context.WithTimeout是创建一个有超时时间设置的context。
6.2 代码块如下
func main() {
ctx, canel := context.WithTimeout(context.Background(), 5*time.Second)
var wg sync.WaitGroup
wg.Add(3)
for i := 0; i < 3; i++ {
go func() {
defer wg.Done()
select {
case <-time.After(6 * time.Second):
fmt.Println("time.After执行")
case <-ctx.Done():
fmt.Println("ctx.Done执行")
}
}()
}
wg.Wait()
canel()
fmt.Println("执行完毕")
time.Sleep(5 * time.Second)
}
6.3 执行效果如下
本文正在参加技术专题18期-聊聊Go语言框架