Go语言 | 并发设计中的同步锁与waitgroup用法
今天是golang专题的第16篇文章,我们一起来聊聊golang当中的并发相关的一些使用。
虽然关于goroutine以及channel我们都已经介绍完了,但是关于并发的机制仍然没有介绍结束。只有goroutine以及channel有时候还是不足以完成我们的问题,比如多个goroutine同时访问一个变量的时候,我们怎么保证这些goroutine之间不会互相冲突或者是影响呢?这可能就需要我们对资源进行加锁或者是采取其他的操作了。
同步锁
golang当中提供了两种常用的锁,一种是sync.Mutex另外一种是sync.RWMutex。我们先说说Mutex,它就是最简单最基础的同步锁,当一个goroutine持有锁的时候,其他的goroutine只能等待到锁释放之后才可以尝试持有。而RWMutex是读写锁的意思,它支持一写多读,也就是说允许支持多个goroutine同时持有读锁,而只允许一个goroutine持有写锁。当有goroutine持有读锁的时候,会阻止写操作。当有goroutine持有写锁的时候,无论读写都会被堵塞。
我们使用的时候需要根据我们场景的特性来决定,如果我们的场景是读操作多过写操作的场景,那么我们可以使用RWMutex。如果是写操作为主,那么使用哪个都差不多。
我们来看下使用的案例,假设我们当前有多个goroutine,但是我们只希望持有锁的goroutine执行,我们可以这么写:
var lock sync.Mutex
for i := 0; i < 10; i++ {
go func() {
lock.Lock()
defer lock.Unlock()
// do something
}()
}
虽然我们用for循环启动了10个goroutine,但是由于互斥锁的存在,同一时刻只能有一个goroutine在执行。
RWMutex区分了读写锁,所以我们一共会有4个api,分别是Lock, Unlock, RLock, RUnlock。Lock和Unlock是写锁的加锁以及解锁,而RLock和RUnlock自然就是读锁的加锁和解锁了。具体的用法和上面的代码一样,我就不多赘述了。
全局操作一次
在一些场景以及一些设计模式当中,会要求我们某一段代码只能执行一次。比如很著名的单例模式,就是将我们经常使用的工具设计成单例,无论运行的过程当中初始化多少次,得到的都是同一个实例。这样做的目的是减去创建实例的时间,尤其是像是数据库连接、hbase连接等这些实例创建的过程非常的耗时。
那我们怎么在golang当中实现单例呢?
有些同学可能会觉得这个很简单啊,我们只需要用一个bool型变量判断一下初始化是否有完成不就可以了吗?比如这样:
type Test struct {}
var test Test
var flag = false
func init() Test{
if !flag {
test = Test{}
flag = true
}
return test
}
看起来好像没有问题,但是仔细琢磨就会发现不对的地方。因为if判断当中的语句并不是原子的,也就是说有可能同时被很多goroutine同时访问。这样的话有可能test这个变量会被多次初始化并且被多次覆盖,直到其中一个goroutine将flag置为true为止。这可能会导致一开始访问的goroutine获得的test都各不相同,而产生未知的风险。
要想要实现单例其实很简单,sync库当中为我们提供了现成的工具once。它可以传入一个函数,只允许全局执行这个函数一次。在执行结束之前,其他goroutine执行到once语句的时候会被阻塞,保证只有一个goroutine在执行once。当once执行结束之后,再次执行到这里的时候,once语句的内容将会被跳过,我们来结合一下代码来理解一下,其实也非常简单。
type Test struct {}
var test Test
func create() {
test = Test{}
}
func init() Test{
once.Do(create)
return test
}
waitgroup
最后给大家介绍一下waitgroup的用法,我们在使用goroutine的时候有一个问题是我们在主程序当中并不知道goroutine执行结束的时间。如果我们只是要依赖goroutine执行的结果,当然可以通过channel来实现。但假如我们明确地希望等到goroutine执行结束之后再执行下面的逻辑,这个时候我们又该怎么办呢?
有人说可以用sleep,但问题是我们并不知道goroutine执行到底需要多少时间,怎么能事先知道需要sleep多久呢?
为了解决这个问题,我们可以使用sync当中的另外一个工具,也就是waitgroup。
waitgroup的用法非常简单,只有三个方法,一个是Add,一个是Done,最后一个是Wait。其实waitgroup内部存储了当前有多少个goroutine在执行,当调用一次Add x的时候,表示当下同时产生了x个新的goroutine。当这些goroutine执行完的时候, 我们让它调用一下Done,表示执行结束了一个goroutine。这样当所有goroutine都执行完Done之后,wait的阻塞会结束。
我们来看一个例子:
sample := Sample{}
wg := sync.WaitGroup{}
go func() {
// 增加一个正在执行的goroutine
wg.Add(1)
// 执行完成之后Done一下
defer wg.Done()
sample.JoinUserFeature()
}()
go func() {
wg.Add(1)
defer wg.Done()
sample.JoinItemFeature()
}()
wg.Wait()
// do something
总结
上面介绍的这些工具和库都是我们日常在并发场景当中经常使用的,也是一个golang工程师必会的技能之一。到这里为止,关于golang这门语言的基本功能介绍就差不多了,后面将会介绍一些实际应用的内容,敬请期待吧。
今天的文章到这里就结束了,如果喜欢本文的话,请来一波素质三连,给我一点支持吧(关注、转发、点赞)。
- END -

Go语言 | 并发设计中的同步锁与waitgroup用法的更多相关文章
- Java Learning:并发中的同步锁(synchronized)
引言 最近一段时间,实验室已经倾巢出动找实习了,博主也凑合了一把,结果有悲有喜,BAT理所应当的跪了,也收到了其他的offer,总的感受是有必要夯实基础啊. 言归正传,最近在看到java多线程的时候, ...
- FPGA异步时钟设计中的同步策略
1 引言 基于FPGA的数字系统设计中大都推荐采用同步时序的设计,也就是单时钟系统.但是实际的工程中,纯粹单时钟系统设计的情况很少,特别是设计模块与外围芯片的通信中,跨时钟域的情况经常不可避免. ...
- Python并发编程-进程 线程 同步锁 线程死锁和递归锁
进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...
- 网络编程基础----并发编程 ---守护进程----同步锁 lock-----IPC机制----生产者消费者模型
1 守护进程: 主进程 创建 守护进程 辅助主进程的运行 设置进程的 daemon属性 p1.daemon=True 1 守护进程会在主进程代码执行结束后就终止: 2 守护进程内无法再开启子进程 ...
- Java并发编程之Lock(同步锁、死锁)
这篇文章是接着我上一篇文章来的. 上一篇文章 同步锁 为什么需要同步锁? 首先,我们来看看这张图. 这是一个程序,多个对象进行抢票. package MovieDemo; public class T ...
- python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio
摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...
- Java中线程同步锁和互斥锁有啥区别?看完你还是一脸懵逼?
首先不要钻概念牛角尖,这样没意义. 也许java语法层面包装成了sycnchronized或者明确的XXXLock,但是底层都是一样的.无非就是哪种写起来方便而已. 锁就是锁而已,避免多个线程对同一个 ...
- verilogHDL设计中的同步时序逻辑
引用自夏宇闻教授 1.同步时序逻辑: 是指表示状态的寄存器组的值只能在唯一确定的触发条件发生改变. 只能由时钟的正跳变沿或者负跳变沿触发的状态机就是一例,always@(posedge clk). 1 ...
- Java 并发编程中的 CountDownLatch 锁用于多个线程同时开始运行或主线程等待子线程结束
Java 5 开始引入的 Concurrent 并发软件包里面的 CountDownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是 ...
随机推荐
- 基于.NetCore3.1系列 —— 日志记录之自定义日志组件
一.前言 回顾:日志记录之日志核心要素揭秘 在上一篇中,我们通过学习了解在.net core 中内置的日志记录中的几大核心要素,在日志工厂记录器(ILoggerFactory)中实现将日志记录提供器( ...
- ElementUI——级联和树形省市区
项目中有用到省市区选择,我们是三个接口获取数据的,在此记录一下级联和树形的区别: 级联HTML: <el-cascader :props="region"></e ...
- Java的注解浅析
人的一生就像一篇文章,只有经过多次精心修改,才能不断完善 Java注解概念理解: Java注解又称为Java标注,是JDK5引入的一中注释机制,Java中大家熟悉的五种注解分别是:@Override, ...
- 【算法•日更•第四十七期】Mac与windows系统的差别
小编最近装了个Mac系统,因为小编已经有笔记本可以用linux了,所以就决定在台式机上装个双系统,结果一不小心把Mac装在C盘上了,哎,说多了都是泪啊. 其实用了Mac之后才发现windows特别好用 ...
- Docker: docker pull, wget, curl, git clone 等如何更快?
1) Docker 配置 1.1) daemon.json 配置镜像 路径: /etc/docker/daemon.json 文档: Config Daemon registry-mirrors 设定 ...
- 关于Java中for循环的i++和++i区别
我们应该都知道i++和++i的区别是: ++i是先执行 i = i +1 再使用 i 的值,而 i++ 是先使用 i 的值再执行 i = i + 1: for循环的执行顺序如下: for(a;b;c) ...
- HMM隐马尔可夫模型来龙去脉(一)
目录 隐马尔可夫模型HMM学习导航 一.认识贝叶斯网络 1.概念原理介绍 2.举例解析 二.马尔可夫模型 1.概念原理介绍 2.举例解析 三.隐马尔可夫模型 1.概念原理介绍 2.举例解析 四.隐马尔 ...
- selenium定位方法(二)
selenium定位方法(二) 1.xpath定位:xpath是在XML中查找节点所在的路径的表达式 1)绝对路径的Xpath表达式 例:/html/body/div/div[1]/ul//li[3 ...
- Protocol buffers--python 实践 简介以及安装与使用
简介: Protocol Buffers以下简称pb,是google开发的一个可以序列化 反序列化object的数据交换格式,类似于xml,但是比xml 更轻,更快,更简单.而且以上的重点突出一个跨平 ...
- JS继承 小白文
继承我的理解是 一个对象 能够使用另一个对象的方法和属性 继承需要一个父类构造函数 一.通过原型链继承 通过 创建一个 Person 的实例对象,该对象身上 不仅有 name 和 age 等方法, 也 ...