Golang 中阻塞的 7 种常见用法
最编程
2024-04-08 10:57:50
...
不同方式的阻塞
在工作和学习总,使用Golang的实现业务逻辑的时候,往往需要使流程阻塞一段时间等待其他协程的执行;或者永久阻塞来监听一些连接信息等。下面提供了几种常见的阻塞方式,仅供参考。
1. sync.WaitGroup
sync.WaitGroup
是 Golang 中常用的并发措施,我们可以用它来等待一批 Goroutine 结束。
- Add(): 计数器+1
- Done(): 计数器-1, 当计数器为0时,唤醒Wait挂起的协程
- Wait(): 挂起当前协程
func chokeWithWaitGroup() {
start := time.Now()
wg := sync.WaitGroup{}
// 增加阻塞计数器
wg.Add(1)
go func() {
time.Sleep(chokeTime)
// 扣减阻塞计数器
wg.Done()
}()
// 等待阻塞计数器到 0
wg.Wait()
d := time.Since(start)
fmt.Println("使用WaitGroup阻塞了:", d)
}
2. select
select
是 Go 中的一个控制结构,类似于 switch 语句。
- select 语句只能用于通道操作,每个 case 必须是一个通道操作,要么是发送要么是接收。
- select 语句会监听所有指定的通道上的操作,一旦其中一个通道准备好就会执行相应的代码块。
- 如果多个通道都准备好,那么 select 语句会随机选择一个通道执行。
- 如果所有通道都没有准备好,那么执行 default 块中的代码
func chokeWithSelect() {
start := time.Now()
// 启动定时器
timer := time.NewTimer(chokeTime)
select {
case <-timer.C:
d := time.Since(start)
fmt.Println("使用Select阻塞了:", d)
}
}
3. channel
Go 语言中的通道channel
是一种特殊的类型。
通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。
当没有给channel设置空间的时候,称为无缓冲通道或者阻塞通道或者同步通道。
func chokeWithChannel() {
start := time.Now()
var ch = make(chan struct{})
go func() {
time.Sleep(chokeTime)
ch <- struct{}{}
}()
// 阻塞,等待channel数据
if _, ok := <-ch; ok {
d := time.Since(start)
fmt.Println("使用channel阻塞了:", d)
}
}
4. time.After
Go 语言中的time包提供了时间的显示和测量用的函数。其中time.After
会在一段时间后返回一个时间信号。且是一次性的定时。
因此,可以通过time.After来阻塞固定时长。
func chokeWithTimeAfter() {
start := time.Now()
<-time.After(chokeTime)
d := time.Since(start)
fmt.Println("使用channel阻塞了:", d)
}
5. for
使用for{}
循环来控制阻塞,这个不需要解释了。但是会一直占用CPU的资源,慎用。
func chokeWithFor() {
start := time.Now()
for {
d := time.Since(start)
if d > chokeTime {
fmt.Println("使用For阻塞了:", d)
return
}
}
}
6. mutex
使用mutex
锁的时候,如果多个协程同时想要获取锁,那么就会出现竞争关系。当第二个协程在尝试获取锁的时候,发现锁已经被其他协程占用,则会一直不停的尝试获取锁,形成阻塞。
func chokeWithMutex() {
start := time.Now()
mu := sync.Mutex{}
mu.Lock()
go func() {
mu.Lock()
defer mu.Unlock()
d := time.Since(start)
fmt.Println("使用mutex阻塞了:", d)
}()
time.Sleep(chokeTime)
mu.Unlock()
time.Sleep(chokeTime)
}
7. goto
goto
是调整代码执行位置,如果形成了一个循环的化,同样会阻塞程序。
func chokeWithGoto() {
start := time.Now()
here:
d := time.Since(start)
if d > chokeTime {
fmt.Println("使用Goto阻塞了:", d)
return
}
goto here
}
完整代码示例
方法
package choke
import (
"fmt"
"sync"
"time"
)
var chokeTime = 1 * time.Second
// chokeWithWaitGroup 使用WaitGroup()控制阻塞
func chokeWithWaitGroup() {
start := time.Now()
wg := sync.WaitGroup{}
// 增加阻塞计数器
wg.Add(1)
go func() {
time.Sleep(chokeTime)
// 扣减阻塞计数器
wg.Done()
}()
// 等待阻塞计数器到 0
wg.Wait()
d := time.Since(start)
fmt.Println("使用WaitGroup阻塞了:", d)
}
// chokeWithSelect 使用select控制阻塞
func chokeWithSelect() {
start := time.Now()
// 启动定时器
timer := time.NewTimer(chokeTime)
select {
case <-timer.C:
d := time.Since(start)
fmt.Println("使用Select阻塞了:", d)
}
}
// chokeWithFor 使用for控制阻塞
func chokeWithFor() {
start := time.Now()
for {
d := time.Since(start)
if d > chokeTime {
fmt.Println("使用For阻塞了:", d)
return
}
}
}
// chokeWithMutex 使用mutex控制阻塞
func chokeWithMutex() {
start := time.Now()
mu := sync.Mutex{}
mu.Lock()
go func() {
mu.Lock()
defer mu.Unlock()
d := time.Since(start)
fmt.Println("使用mutex阻塞了:", d)
}()
time.Sleep(chokeTime)
mu.Unlock()
time.Sleep(chokeTime)
}
// chokeWithChannel 使用channel控制阻塞
func chokeWithChannel() {
start := time.Now()
var ch = make(chan struct{})
go func() {
time.Sleep(chokeTime)
ch <- struct{}{}
}()
// 阻塞,等待channel数据
if _, ok := <-ch; ok {
d := time.Since(start)
fmt.Println("使用channel阻塞了:", d)
}
}
// chokeWithTimeAfter 使用time.After控制阻塞
func chokeWithTimeAfter() {
start := time.Now()
<-time.After(chokeTime)
d := time.Since(start)
fmt.Println("使用channel阻塞了:", d)
}
// chokeWithGoto 使用goto控制阻塞
func chokeWithGoto() {
start := time.Now()
here:
d := time.Since(start)
if d > chokeTime {
fmt.Println("使用Goto阻塞了:", d)
return
}
goto here
}
测试
package choke
import "testing"
func TestChoke(t *testing.T) {
chokeWithWaitGroup()
chokeWithSelect()
chokeWithChannel()
chokeWithTimeAfter()
chokeWithFor()
chokeWithMutex()
chokeWithGoto()
}
执行结果
=== RUN TestChoke
使用WaitGroup阻塞了: 1.000691546s
使用Select阻塞了: 1.001008044s
使用For阻塞了: 1.000650866s
使用mutex阻塞了: 1.000067636s
使用channel阻塞了: 1.00013111s
使用channel阻塞了: 1.000485065s
使用Goto阻塞了: 1.000000004s
--- PASS: TestChoke (8.00s)
PASS
Process finished with the exit code 0
上一篇: 使用 Goland 破解永久版进行安装
推荐阅读
-
Java 枚举(枚举)的 7 种常见用法 - 使用 4:覆盖枚举方法
-
Golang 中阻塞的 7 种常见用法
-
类型在 golang 中的常见用法
-
统计学习 04:假设检验(以 t 检验为例)和 P 值 - 要点 I. 假设检验的一般思路 假设检验 清楚你的问题是什么?期望得出什么结论? 例如,两种药物的疗效是否存在差异,自变量与因变量之间是否存在回归关系 .... 请始终牢记,假设检验回答的是是否存在某种关系的问题:它并不衡量这种关系有多大。 提出两种假设:零假设 (H0) 和备择假设 (H1) 零假设与备择假设相反,一般来说,研究的目的是证明原假设是错误的,即得出备择假设的结论。 例如,如果实验预期希望两种药物的疗效存在差异,那么 H0:μ1 - μ2 = 0;H1:μ1 - μ2 ≠ 0 H0:μ1-μ2 = 0 的一般形式称为双侧检验,而 >、<等零假设称为单侧检验。一般来说双侧检验更为常见,下面也主要介绍这种方法。 单尾或双尾测试 根据原始数据计算零假设概率分布的统计量(t 值、Z 值、F 值等)。 根据问题的性质选择合适的概率检验方法,从而计算出相应的统计量值;因此,不同情况的统计量值有不同的计算方法。 根据计算出的统计量值,利用统计软件,可以知道相应的 p 值是多少 也可以先确定一个合适的显著性水平(0.0.001....),并计算其临界值,再与我们计算出的统计量值进行比较,从而做出判断。 根据第四步的比较结果,如果 p 值小于预期的显著性水平(α,通常设定为 0.05),则认为该统计量远离原假设分布,属于小概率事件,则拒绝原假设,从而接受备择假设。 决定 要点 2:以 t 检验为例,演示上述假设检验思路。 t 检验基于 t 分布,常见的 t 检验有三种,如下图所示,但我认为第三种配对设计可能更常用(零假设:差异是否为零),下面介绍的例子就是一种配对设计 三次 t 检验 举例测量两组大鼠肝脏中维生素 A 的含量,比较两组大鼠维生素 A 含量是否有差异。数据如下 数据 (1) 预计两组大鼠的维生素 A 水平存在差异 (2) H0:μd=0,H1:μd≠0,α=0.05,双侧检验 (3) t 统计量的计算 配方 计算 上述程序计算的是*度为 7 的 t 分布情况下的 t 值。只需理解公式即可,不同的方法有不同的公式,这些交给统计软件即可。