Go并发编程-条件变量

一、前言

go语言类似Java JUC包也提供了一些列用于多线程之间进行同步的措施,比如低级的同步措施有 锁、CAS、原子变量操作类。本节我们先来看看go中的条件变量,在java中条件变量是与具体的锁相关联的,在go中也是这样的。

二、条件变量

在go中使用 var cond sync. NewCond(l Locker)可以创建一个与锁l相关的条件变量cond,这个锁l可以是Mutex 或者RWMutex类型的锁,该方法会返回Cond类型的指针被称为条件变量。条件变量与Java中类似,主要是使用通知/等待做多线程之间的同步,Cond类型主要有三个方法:

  • (c *Cond) Wait():等待方法,当一个goroutine调用cond的Wait()方法后,当前goroutine会被阻塞,直到有其他goroutine调用了该cond变量的Signal()或者Broadcast()方法;需要注意的是调用Wait()方法前,当前goroutine要通过cond.l.Lock() 先获取与cond关联的锁l,否则会抛出异常 fatal error:sync:unlock of unlocked mutex,这点与java相同:

package main
import (
    "sync"
)
var (
    lock sync.Mutex            //互斥锁
    cond = sync.NewCond(&lock) //条件变量
)
func main() {
    cond.Wait()
}

另外某个goroutine调用条件变量的Wait()方法是为了等待某个事件的发生,而当其他goroutine调用该条件变量的Singal()或者Broadcast()方法时候,等待的goroutine机会被唤醒,但是这时候等待的goroutine的事件未必已经发生了,所以一般goroutine调用条件变量的Wait()方法时候都是使用循环检查条件是否达到的方式:

package main
import (
    "sync"
)
var (
    lock sync.Mutex            //互斥锁
    cond = sync.NewCond(&lock) //条件变量
)
func main() {
    cond.L.Lock() //1获取条件变量关联的锁
    //2循环判断自己需要的事情是否发生
    for !condition() {
        cond.Wait()
    }
    //3.获取到自己需要的事件后做一些事情dosometing
    //4.释放锁
    cond.L.Unlock()
}
func condition() bool {
    //
    if 判断自己需要的事件已经发生 {
        return true
    } else {
        return false
    }
}

如上代码main内使用condition()方法判断自己需要的事件是否发生,如果没有则阻塞自己进行等待。如果其他调用该条件变量的Singal()或者Broadcast()方法时候,等待的goroutine机会被唤醒,然后循环一次再次调用condition()方法判断自己需要的事件是否达到了,如果没有则继续调用条件变量的Wait()方法阻塞自己,否则就继续向下运行。

另外需要注意的是当某个goroutine调用了条件变量的Wait()方法后,当前goroutine会先释放获取的与该条件变量相关的锁,然后在阻塞自己。

  • (c *Cond) Signal(),通知方法,当一个goroutine调用了条件变量c的该方法后,就会激活一个由于调用c的Wait()方法而被阻塞的goroutine。需要注意的是在调用c.Signal()方法前,不需要先获取与c关联的锁,这点与调用c.Wait()方法不同.

package main
import (
    "fmt"
    "sync"
    "sync/atomic"
    "time"
)
var (
    lock sync.Mutex            //互斥锁
    cond = sync.NewCond(&lock) //条件变量
    flag int32 //标签
)
func main() {
    cond.L.Lock() //1获取条件变量关联的锁
    go func() {
        //休眠5秒,模拟远程调用
        time.Sleep(5 * time.Second)
        //设置条件完成
        atomic.CompareAndSwapInt32(&flag, 0, 1)
        cond.Signal() //通知
        fmt.Println("---sub goroutine  rpc call done---")
    }()
    //2循环判断自己需要的事情是否发生
    fmt.Println("---main wait rpc call over---")
    for !condition() {
        cond.Wait()
    }
    fmt.Println("---main  do something---")
    //3.获取到自己需要的事件后做一些事情dosometing
    //4.释放锁
    cond.L.Unlock()
}
func condition() bool {
    if 1 == atomic.LoadInt32(&flag) {
        return true
    } else {
        return false
    }
}

如上例子main goroutine代码1先获取了锁,然后代码2开启了一个子goroutine,其内部使用sleep函数模拟了一次远程rpc调用,等调用完成后,原子性更新flag变量的值从0变为1,标识rpc已经完成,然后调用条件变量的Signal方法激活阻塞的一个goroutine。需要注意的是这里子goroutine在调用条件变量Signal方法前并没获取条件变量关联的锁。

main goroutine内代码3使用经典的循环方式等待rpc的调用完成,条件是condition() 函数,由于一开始flag的初始化值为0,所以main goroutine调用了条件变量的Wait()方法阻塞了自己。当子goroutine完成rpc调用,调用条件变量的Signal方法后,main goroutine就返回了,然后再次调用condition() 方法发现flag为true了说明自己等待的rpc已经完成了,就继续向下执行。

  • (c *Cond) Broadcast() 通知方法,当一个goroutine调用了条件变量c的该方法后,就会激活所有由于调用c的Wait()方法而被阻塞的goroutine。需要注意的是在调用c.Broadcast()方法前,不需要先获取与c关联的锁,这点与调用c.Wait()方法不同.

package main
import (
    "fmt"
    "sync"
    "time"
)
var (
    lock sync.Mutex            //互斥锁
    cond = sync.NewCond(&lock) //条件变量
    wg sync.WaitGroup
)
func main() {
    wg.Add(2)
    //1.
    go func() {
        cond.L.Lock()
        fmt.Println("---sub goroutine1  got lock and wait---")
        cond.Wait()
        fmt.Println("---sub goroutine1  wait return---")
        cond.L.Unlock()
    }()
    //2.
    go func() {
        cond.L.Lock()
        fmt.Println("---sub goroutine2  got lock and wait---")
        cond.Wait()
        fmt.Println("---sub goroutine2  wait return---")
        cond.L.Unlock()
    }()
    //3.
    time.Sleep(3 * time.Second)
    //4.
    cond.Signal()
    //5
    time.Sleep(5 * time.Second)
}

如上代码我们开启了两个goroutine分别调用条件变变量的Wait()方法,然后在main函数里面调用条件变量的Signal()方法,运行上面代码会发现只有一个goroutine会打印出sub * wait return。如果把代码4修改为cond.Broadcast()则会发现两个goroutine都会打印出return说明两个goroutine都被通知返回了。

三、总结

go中条件变量与Java中条件变量类似,但是也有不同,相同在于条件变量都是与锁关联的,并且只有当线程获取到锁后才可以调用其关联的条件变量的wait方法,否则会抛出异常,另外当线程阻塞到wait方法后,当前线程会释放已经获取的锁。不同在于Java中只有当线程获取到锁后才可以调用其关联的条件变量的signal方法,否则会抛出异常,但是在go中调用线程调用signal前获取锁不是必须的。

假期在家无聊?那就免费学习下Go语言吧!!!

Go并发编程-并发与并行

Go并发编程-并发编程难在哪里

Go并发编程-线程模型

Go并发编程-内存模型

Go并发编程-goroutine轻量级的线程

Go并发编程-runtime包

Go并发编程-互斥锁

Go并发编程-读写锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值