利用管道(channel)实现同步

GO语言实现同步的方式有很多,互斥量(sync.Mutex)、channel通信等等;
今天总结一下Go中的channel方式!

channel通信包含两种模式:一种是带无缓冲的channel,另一种是有缓冲的channel;

1、无缓冲方式

在无缓存的channel上的每一次发送操作都会有对应的接受操作相配对,发送和接受操作通常发生在两个goroutine上(在同一个goroutine上执行两个操作容易导致死锁),无缓存的channel上的发送操作总是在接受操作之前发生;

首先通过无缓冲方式来实现同步:

func main() {
    done := make(chan int)

    go func(){
        fmt.Println("hello world!")
        <-done
    }()

    done <- 1
}

根据GO语言内存模型规范,对于从无缓冲channel进行的接收,发生在对该channel进行的发送完成之前。因此后台线程完成 <- done 接收操作之后,main函数的 done <- 1 发送操作才有可能完成(从而退出main、退出程序),而此时打印工作已经完成。

上述代码虽然可以完成同步,但是对管道的缓冲大小太敏感了,如果管道有缓冲的话就不能够保证main退出后台线程正常打印

更好的做法是将接收和发送方向调换一下,这样可以避免同步受缓冲大小的影响;

func main() {
    done := make(chan int, 1) // 带缓存的管道

    go func(){
        fmt.Println("hello world!")
        done <- 1
    }()

    <-done
}

2、有缓冲的方式

对于带缓冲的Channel,对于Channel的第K个接收完成操作发生在第K+C个发送操作完成之前,其中C是Channel的缓存大小。虽然管道是带缓存的,main线程接收完成是在后台线程发送开始但还未完成的时刻,此时打印工作也是已经完成的。

基于带缓存的管道,我们可以很容易将打印线程扩展到N个。下面的例子是开启10个后台线程分别打印:

func main() {
    done := make(chan int, 10) // 带 10 个缓存

    // 开N个后台打印线程
    for i := 0; i < cap(done); i++ {
        go func(){
            fmt.Println("hello world!")
            done <- 1
        }()
    }

    // 等待N个后台线程完成
    for i := 0; i < cap(done); i++ {
        <-done
    }
}

对于这种要等待N个线程完成后再进行下一步的同步操作有一个简单的做法,就是使用sync.WaitGroup来等待一组事件:

func main() {
    var wg sync.WaitGroup

    // 开N个后台打印线程
    for i := 0; i < 10; i++ {
        wg.Add(1)

        go func() {
            fmt.Println("hello world!")
            wg.Done()
        }()
    }

    // 等待N个后台线程完成
    wg.Wait()
}

其中wg.Add(1)用于增加等待事件的个数,必须确保在后台线程启动之前执行(如果放到后台线程之中执行则不能保证被正常执行到)。当后台线程完成打印工作之后,调用wg.Done()表示完成一个事件。main函数的wg.Wait()是等待全部的事件完成

个人github账号:https://github.com/SpecialAll

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值