Go并发编程sync.Cond的具体使用_第1页
Go并发编程sync.Cond的具体使用_第2页
Go并发编程sync.Cond的具体使用_第3页
Go并发编程sync.Cond的具体使用_第4页
Go并发编程sync.Cond的具体使用_第5页
已阅读5页,还剩3页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

第Go并发编程sync.Cond的具体使用目录简介详细介绍案例:Redis连接池注意点

简介

Go标准库提供Cond原语的目的是,为等待/通知场景下的并发问题提供支持。Cond通常应用于等待某个条件的一组goroutine,等条件变为true的时候,其中一个goroutine或者所有的goroutine都会被唤醒执行。

Cond是和某个条件相关,这个条件需要一组goroutine协作共同完成,在条件还没有满足的时候,所有等待这个条件的goroutine都会被阻塞住,只有这一组goroutine通过协作达到了这个条件,等待的goroutine才可能继续进行下去。

这个条件可以是我们自定义的true/false逻辑表达式。

但是Cond使用的比较少,因为在大部分场景下是可以被Channel和WaitGroup来替换的。

详细介绍

下面就是Cond的数据结构和对外提供的方法,Cond内部维护了一个等待队列和锁实例。

typeCondstruct{

noCopynoCopy

//锁

LLocker

//等待队列

notifynotifyList

checkercopyChecker

funcNeWCond(lLocker)*Cond

func(c*Cond)Broadcast()

func(c*Cond)Signal()

func(c*Cond)Wait()

NeWCond:NeWCond方法需要调用者传入一个Locker接口,这个接口就Lock/UnLock方法,所以我们可以传入一个sync.Metex对象

Signal:允许调用者唤醒一个等待当前Cond的goroutine。如果Cond等待队列中有一个或者多个等待的goroutine,则从等待队列中移除第一个goroutine并把它唤醒

Broadcast:允许调用者唤醒所有等待当前Cond的goroutine。如果Cond等待队列中有一个或者多个等待的goroutine,则清空所有等待的goroutine,并全部唤醒

Wait:会把调用者放入Cond的等待队列中并阻塞,直到被Signal或者Broadcast的方法从等待队列中移除并唤醒

案例:Redis连接池

可以看一下下面的代码,使用了Cond实现一个Redis的连接池,最关键的代码就是在链表为空的时候需要调用Cond的Wait方法,将gorutine进行阻塞。然后goruntine在使用完连接后,将连接返回池子后,需要通知其他阻塞的goruntine来获取连接。

packagemain

import(

"container/list"

"fmt"

"math/rand"

"sync"

"time"

//连接池

typePoolstruct{

locksync.Mutex//锁

clientslist.List//连接

cond*sync.Cond//cond实例

closebool//是否关闭

//RedisClient

typeClientstruct{

idint32

//创建RedisClient

funcNewClient()*Client{

returnClient{

id:rand.Int31n(100000),

//关闭RedisClient

func(this*Client)Close(){

fmt.Printf("Client:%d正在关闭",this.id)

//创建连接池

funcNewPool(maxConnNumint)*Pool{

pool:=new(Pool)

pool.cond=sync.NewCond(pool.lock)

//创建连接

fori:=0;imaxConnNum;i++{

client:=NewClient()

pool.clients.PushBack(client)

returnpool

//从池子中获取连接

func(this*Pool)Pull()*Client{

this.lock.Lock()

deferthis.lock.Unlock()

//已关闭

ifthis.close{

fmt.Println("Poolisclosed")

returnnil

//如果连接池没有连接需要阻塞

forthis.clients.Len()=0{

this.cond.Wait()

//从链表中取出头节点,删除并返回

ele:=this.clients.Remove(this.clients.Front())

returnele.(*Client)

//将连接放回池子

func(this*Pool)Push(client*Client){

this.lock.Lock()

deferthis.lock.Unlock()

ifthis.close{

fmt.Println("Poolisclosed")

return

//向链表尾部插入一个连接

this.clients.PushBack(client)

//唤醒一个正在等待的goruntine

this.cond.Signal()

//关闭池子

func(this*Pool)Close(){

this.lock.Lock()

deferthis.lock.Unlock()

//关闭连接

fore:=this.clients.Front();e!=nil;e=e.Next(){

client:=e.Value.(*Client)

client.Close()

//重置数据

this.close=true

this.clients.Init()

funcmain(){

varwgsync.WaitGroup

pool:=NewPool(3)

fori:=1;i=10;i++{

wg.Add(1)

gofunc(indexint){

deferwg.Done()

//获取一个连接

client:=pool.Pull()

fmt.Printf("Time:%s|【goruntine#%d】获取到client[%d]\n",time.Now().Format("15:04:05"),index,client.id)

time.Sleep(time.Second*5)

fmt.Printf("Time:%s|【goruntine#%d】使用完毕,将client[%d]放回池子\n",time.Now().Format("15:04:05"),index,client.id)

//将连接放回池子

pool.Push(client)

}(i)

wg.Wait()

}

运行结果:

Time:15:10:25|【goruntine#7】获取到client[31847]

Time:15:10:25|【goruntine#5】获取到client[27887]

Time:15:10:25|【goruntine#10】获取到client[98081]

Time:15:10:30|【goruntine#5】使用完毕,将client[27887]放回池子

Time:15:10:30|【goruntine#6】获取到client[27887]

Time:15:10:30|【goruntine#10】使用完毕,将client[98081]放回池子

Time:15:10:30|【goruntine#7】使用完毕,将client[31847]放回池子

Time:15:10:30|【goruntine#1】获取到client[31847]

Time:15:10:30|【goruntine#9】获取到client[98081]

Time:15:10:35|【goruntine#6】使用完毕,将client[27887]放回池子

Time:15:10:35|【goruntine#3】获取到client[27887]

Time:15:10:35|【goruntine#1】使用完毕,将client[31847]放回池子

Time:15:10:35|【goruntine#4】获取到client[31847]

Time:15:10:35|【goruntine#9】使用完毕,将client[98081]放回池子

Time:15:10:35|【goruntine#2】获取到client[98081]

Time:15:10:40|【goruntine#3】使用完毕,将client[27887]放回池子

Time:15:10:40|【goruntine#8】获取到client[27887]

Time:15:10:40|【goruntine#2】使用完毕,将client[98081]放回池子

Time:15:10:40|【goruntine#4】使用完毕,将client[31847]放回池子

Time:15:10:45|【goruntine#8】使用完毕,将client[27887]放回池子

注意点

在调用Wait方法前,需要先加锁,就像我上面例子中Pull方法也是先加锁

看一下源码就知道了,因为Wait方法的执行逻辑是先将goruntine添加到等待队列中,然后释放锁,然后阻塞,等唤醒后,会继续加锁。如果在调用Wait前不加锁,但是里面会解锁,执行的时候就会报错。

//

//c.L.Lock()

//for!condition(){

//c.Wait()

//...makeuseofcondition...

//c.L.Unlock()

func(c*Cond)Wait(){

c.checker.check()

//添加到等待队列

t:=runtime_notifyListAdd(c.notify)

c.L.Unlock()

//阻塞

runtime_notifyListWait(c.notify,t)

c.L.Lock()

}

还是Wait方法,在唤醒后需要继续检查Cond条件

就拿上面的redis连接案例来进行说明吧,我这里是使用了for循环来进行检测。如果将for循环改成使用if,也就是只判断一次,会有什么问题?可以停下来先想想

上面说了调用者也可以使用Broadcast方法来唤醒goruntine,如果使用

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论