前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在Golang中常见的7种阻塞使用方式

在Golang中常见的7种阻塞使用方式

原创
作者头像
KunkkaWu
发布2023-04-06 11:31:12
3.4K1
发布2023-04-06 11:31:12
举报
文章被收录于专栏:算法协议算法协议

不同方式的阻塞

在工作和学习总,使用Golang的实现业务逻辑的时候,往往需要使流程阻塞一段时间等待其他协程的执行;或者永久阻塞来监听一些连接信息等。下面提供了几种常见的阻塞方式,仅供参考。

1. sync.WaitGroup

sync.WaitGroup 是 Golang 中常用的并发措施,我们可以用它来等待一批 Goroutine 结束。

  • Add(): 计数器+1
  • Done(): 计数器-1, 当计数器为0时,唤醒Wait挂起的协程
  • Wait(): 挂起当前协程
代码语言:go
复制
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 块中的代码
代码语言:go
复制
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设置空间的时候,称为无缓冲通道或者阻塞通道或者同步通道。

代码语言:go
复制
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来阻塞固定时长。

代码语言:go
复制
func chokeWithTimeAfter() {
    start := time.Now()
    <-time.After(chokeTime)
    d := time.Since(start)
    fmt.Println("使用channel阻塞了:", d)
}

5. for

使用for{}循环来控制阻塞,这个不需要解释了。但是会一直占用CPU的资源,慎用。

代码语言:go
复制
func chokeWithFor() {
    start := time.Now()
    for {
        d := time.Since(start)
        if d > chokeTime {
            fmt.Println("使用For阻塞了:", d)
            return
        }
    }
}

6. mutex

使用mutex锁的时候,如果多个协程同时想要获取锁,那么就会出现竞争关系。当第二个协程在尝试获取锁的时候,发现锁已经被其他协程占用,则会一直不停的尝试获取锁,形成阻塞。

代码语言:go
复制
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 是调整代码执行位置,如果形成了一个循环的化,同样会阻塞程序。

代码语言:go
复制
func chokeWithGoto() {
    start := time.Now()
here:
    d := time.Since(start)
    if d > chokeTime {
        fmt.Println("使用Goto阻塞了:", d)
        return
    }
    goto here
}

完整代码示例

方法

代码语言:go
复制
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
}

测试

代码语言:go
复制
package choke

import "testing"

func TestChoke(t *testing.T) {
    chokeWithWaitGroup()
    chokeWithSelect()
    chokeWithChannel()
    chokeWithTimeAfter()
    chokeWithFor()
    chokeWithMutex()
    chokeWithGoto()
}

执行结果

代码语言:shell
复制
=== 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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 不同方式的阻塞
    • 1. sync.WaitGroup
      • 2. select
        • 3. channel
          • 4. time.After
            • 5. for
              • 6. mutex
                • 7. goto
                • 完整代码示例
                  • 方法
                    • 测试
                      • 执行结果
                      领券
                      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
                      http://www.vxiaotou.com