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 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是 ...
随机推荐
- C#LeetCode刷题之#485-最大连续1的个数(Max Consecutive Ones)
问题 该文章的最新版本已迁移至个人博客[比特飞],单击链接 https://www.byteflying.com/archives/3714 访问. 给定一个二进制数组, 计算其中最大连续1的个数. ...
- 制作qq简易聊天框
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...
- 对于python装饰器结合递归的进一步理解
对于python装饰器结合递归的进一步理解 代码如下: import functools def memoize(fn): print('start memoize') known = dict() ...
- AS 新电脑clone项目报错:Clone failed: Authentication failed for 'https://gitee.com/XXX/Demo.git/'
在新的电脑上安装Android Studio,并且使用git clone 项目,报以下错误: Clone failed: Authentication failed for 'https://gite ...
- JavaScript对象、函数、变量、字符串的处理、运算符
一.对象 使用一种抽象的概念去描述,人{属性,方法} var car={type:"BYD",model:500,color:white,do:function(){"可 ...
- Java多线程_CAS算法和ABA问题
CAS算法概述CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换.CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B. CAS指令执行时,当且仅当内存地址V ...
- 理解传输层中UDP协议首部校验和以及校验和计算方法的Java实现
UDP,全称User Datagram Protocol,用户数据报协议,是TCP/IP四层参考模型中传输层的一种面向报文的.无连接的.不能保证可靠的.无拥塞控制的协议.UDP协议因为传输效率高,常用 ...
- 自动部署Asp.Net Core到Docker
原文链接:个人博客:自动部署Asp.Net Core至Docker 本文简介 最近在开发一个管理系统,代码框架是用的前后台分离的方式 后台使用的是Asp.Net Core平台,开发所有业务,向前台提供 ...
- 使用xShell 连接 docker 使用说明
方式一:当不知道docker里镜像的root密码的时候 1.从Docker Hub下载需要的镜像 docker pull 镜像名字 2.使用docker run命令启动容器 docker run -i ...
- 数据处理 | pandas入门专题——离散化与one-hot
今天是pandas数据处理专题第7篇文章,可以点击上方专辑查看往期文章. 在上一篇文章当中我们介绍了对dataframe进行排序以及计算排名的一些方法,在今天的文章当中我们来了解一下dataframe ...