Go并发编程sync.Cond的具体使用
作者:麦超 发布时间:2024-05-13 10:41:00
简介
Go
标准库提供 Cond
原语的目的是,为等待 / 通知场景下的并发问题提供支持。Cond
通常应用于等待某个条件的一组 goroutine
,等条件变为 true
的时候,其中一个 goroutine
或者所有的 goroutine
都会被唤醒执行。
Cond
是和某个条件相关,这个条件需要一组 goroutine
协作共同完成,在条件还没有满足的时候,所有等待这个条件的 goroutine
都会被阻塞住,只有这一组 goroutine
通过协作达到了这个条件,等待的 goroutine 才可能继续进行下去。
这个条件可以是我们自定义的 true/false
逻辑表达式。
但是 Cond
使用的比较少,因为在大部分场景下是可以被 Channel
和 WaitGroup
来替换的。
详细介绍
下面就是 Cond
的数据结构和对外提供的方法,Cond
内部维护了一个等待队列和锁实例。
type Cond struct {
noCopy noCopy
// 锁
L Locker
// 等待队列
notify notifyList
checker copyChecker
}
func NeWCond(l Locker) *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
来获取连接。
package main
import (
"container/list"
"fmt"
"math/rand"
"sync"
"time"
)
// 连接池
type Pool struct {
lock sync.Mutex // 锁
clients list.List // 连接
cond *sync.Cond // cond实例
close bool // 是否关闭
}
// Redis Client
type Client struct {
id int32
}
// 创建Redis Client
func NewClient() *Client {
return &Client{
id: rand.Int31n(100000),
}
}
// 关闭Redis Client
func (this *Client) Close() {
fmt.Printf("Client:%d 正在关闭", this.id)
}
// 创建连接池
func NewPool(maxConnNum int) *Pool {
pool := new(Pool)
pool.cond = sync.NewCond(&pool.lock)
// 创建连接
for i := 0; i < maxConnNum; i++ {
client := NewClient()
pool.clients.PushBack(client)
}
return pool
}
// 从池子中获取连接
func (this *Pool) Pull() *Client {
this.lock.Lock()
defer this.lock.Unlock()
// 已关闭
if this.close {
fmt.Println("Pool is closed")
return nil
}
// 如果连接池没有连接 需要阻塞
for this.clients.Len() <= 0 {
this.cond.Wait()
}
// 从链表中取出头节点,删除并返回
ele := this.clients.Remove(this.clients.Front())
return ele.(*Client)
}
// 将连接放回池子
func (this *Pool) Push(client *Client) {
this.lock.Lock()
defer this.lock.Unlock()
if this.close {
fmt.Println("Pool is closed")
return
}
// 向链表尾部插入一个连接
this.clients.PushBack(client)
// 唤醒一个正在等待的goruntine
this.cond.Signal()
}
// 关闭池子
func (this *Pool) Close() {
this.lock.Lock()
defer this.lock.Unlock()
// 关闭连接
for e := this.clients.Front(); e != nil; e = e.Next() {
client := e.Value.(*Client)
client.Close()
}
// 重置数据
this.close = true
this.clients.Init()
}
func main() {
var wg sync.WaitGroup
pool := NewPool(3)
for i := 1; i <= 10; i++ {
wg.Add(1)
go func(index int) {
defer wg.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()
// }
// ... make use of condition ...
// 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
,如果使用的是 Broadcast
方法,所有的 goruntine
都会被唤醒,然后大家都去链表中去获取 redis
连接了,就会出现部分 goruntine
拿不到连接,实际上没有那么多连接可以获取,因为每次只会放回一个连接到池子中。
// 如果连接池没有连接 需要阻塞
for this.clients.Len() <= 0 {
this.cond.Wait()
}
// 获取连接
ele := this.clients.Remove(this.clients.Front())
return ele.(*Client)
来源:https://juejin.cn/post/7093041338836320292
猜你喜欢
- 今天小池提出一个问题讨论,如何使分页做的更友好。做了一些调研和思考,做了些总结。分页在电商网站3级页、搜索结果页面等信息量大的页面是很重要的
- 对网站的LOGO设计做了一些归纳,希望得到批评,发现写的太长了,又不忍心删减,就分成两部分了,第一部分是有关设计基础的。第二部分是关于网站L
- MySQL 报错:Parameter index out of range (1 > number of parameters, which
- 本文介绍FCKeditor在Java环境下的使用方法。一、简介 功能:所见即所得,支持图片和Flash,工具栏可自由配置,使用简单兼容性:I
- 具体代码如下所示:#!/usr/bin/python# coding=utf-8from ftplib import FTPimport t
- Tkinter是python的GUI模块,内含各种窗口控件,利用其中messagbox可以制作各种信息弹出窗口。以下是制作信息提示框的代码:
- 提示: 利用单表简单查询和多表高级查询技能,并且根据查询要求灵活使用内连接查询、外连接查询或子查询等。同时还利用内连接查询的两种格式、三种外
- 在我们开始之前,一定要注意这篇文章只针对Windows用户!对于那些使用Windows的人来说,这是一个有趣的想法。如果您想使用python
- 这篇文章主要介绍了python GUI自动化实现绕过验证码登录,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,
- mysql存储引擎:MySQL服务器采用了模块化风格,各部分之间保持相对独立,尤其体现在存储架构上。存储引擎负责管理数据存储,以及MySQL
- 核心代码是 getCookie()部分,控制弹框的显示隐藏则在 created()中。<template> <div v-
- 一、前言一个非常强的反爬虫方案 —— 禁用所有 HTTP 1.x 的请求!现在很多爬虫库其实对 HTTP/2.0 支持得不好,比如
- 本文主要介绍Python3.9的一些新特性,如:更快速的进程释放,性能的提升,简便的新字符串函数,字典并集运算符以及更兼容稳定的内部API,
- 缘由新手学习 Django 当配置好 HTML 页面后,就需要使用一些静态资源,如图片,JS 文件,CSS 样式等,但是 Django 里面
- 前面使用TensorFlow实现一个完整的Softmax Regression,并在MNIST数据及上取得了约92%的正确率。前文传送门:
- 导语每次回家小编的身边都会聚集着一堆小朋友,这就是家住一个村的好处。一回家就接收到七大姑八大姨的亲切的问候,关系那是特别不错的,小朋友也不怕
- 前言随着行业的发展,编程能力逐渐成为软件测试从业人员的一项基本能力。因此在笔试和面试中常常会有一定量的编码题,主要考察以下几点。基本编码能力
- 一、平稳序列建模步骤假如某个观察值序列通过序列预处理可以判定为平稳非白噪声序列,就可以利用ARMA模型对该序列进行建模。建模的基本步骤如下:
- 一提到python,大家经常会提到爬虫,爬虫近来兴起的原因我觉得主要还是因为大数据的原因,大数据导致了我们的数据不在只存在于自己的服务器,而
- ASP中的全角和半角转化函数,使用方法,传入要转换的字符给str即可,flag设置要转换的类型。<% Function&n