今天是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用法的更多相关文章

  1. Java Learning:并发中的同步锁(synchronized)

    引言 最近一段时间,实验室已经倾巢出动找实习了,博主也凑合了一把,结果有悲有喜,BAT理所应当的跪了,也收到了其他的offer,总的感受是有必要夯实基础啊. 言归正传,最近在看到java多线程的时候, ...

  2. FPGA异步时钟设计中的同步策略

    1 引言    基于FPGA的数字系统设计中大都推荐采用同步时序的设计,也就是单时钟系统.但是实际的工程中,纯粹单时钟系统设计的情况很少,特别是设计模块与外围芯片的通信中,跨时钟域的情况经常不可避免. ...

  3. Python并发编程-进程 线程 同步锁 线程死锁和递归锁

    进程是最小的资源单位,线程是最小的执行单位 一.进程 进程:就是一个程序在一个数据集上的一次动态执行过程. 进程由三部分组成: 1.程序:我们编写的程序用来描述进程要完成哪些功能以及如何完成 2.数据 ...

  4. 网络编程基础----并发编程 ---守护进程----同步锁 lock-----IPC机制----生产者消费者模型

    1  守护进程: 主进程 创建 守护进程   辅助主进程的运行 设置进程的 daemon属性 p1.daemon=True 1 守护进程会在主进程代码执行结束后就终止: 2 守护进程内无法再开启子进程 ...

  5. Java并发编程之Lock(同步锁、死锁)

    这篇文章是接着我上一篇文章来的. 上一篇文章 同步锁 为什么需要同步锁? 首先,我们来看看这张图. 这是一个程序,多个对象进行抢票. package MovieDemo; public class T ...

  6. python 多线程中的同步锁 Lock Rlock Semaphore Event Conditio

    摘要:在使用多线程的应用下,如何保证线程安全,以及线程之间的同步,或者访问共享变量等问题是十分棘手的问题,也是使用多线程下面临的问题,如果处理不好,会带来较严重的后果,使用python多线程中提供Lo ...

  7. Java中线程同步锁和互斥锁有啥区别?看完你还是一脸懵逼?

    首先不要钻概念牛角尖,这样没意义. 也许java语法层面包装成了sycnchronized或者明确的XXXLock,但是底层都是一样的.无非就是哪种写起来方便而已. 锁就是锁而已,避免多个线程对同一个 ...

  8. verilogHDL设计中的同步时序逻辑

    引用自夏宇闻教授 1.同步时序逻辑: 是指表示状态的寄存器组的值只能在唯一确定的触发条件发生改变. 只能由时钟的正跳变沿或者负跳变沿触发的状态机就是一例,always@(posedge clk). 1 ...

  9. Java 并发编程中的 CountDownLatch 锁用于多个线程同时开始运行或主线程等待子线程结束

    Java 5 开始引入的 Concurrent 并发软件包里面的 CountDownLatch 其实可以把它看作一个计数器,只不过这个计数器的操作是原子操作,同时只能有一个线程去操作这个计数器,也就是 ...

随机推荐

  1. JavaScript中的正则表达式详解

    摘要:javascript中的正则表达式作为相当重要的知识,本文将介绍正则表达式的相关知识和用法. 正则表达式(Regular Expression)是一门简单语言的语法规范,是强大.便捷.高效的文本 ...

  2. Salesforce学习笔记之lwc

    1.在Flow中使用Lightning Web Component(lwc)时,发现一个问题.给这个组件设置了一个输入参数,其值取自前一个Screen.第一次运行时,参数的值正确,但是如果这时退回前一 ...

  3. 快速搭建一个Vue-cli项目(简单到爆炸)

    引言: 2013-2020年,这7年是web前端技术的一个高速发展期,也是前端开发岗位的考验期. 在我接触前端时,JQuery十分热门,开发者从原生JS到JQ的应用,可以说是大大提高了开发效率,也被广 ...

  4. MySQL数据库下统计记录数小于指定数值的数据

    楼主在做一个智慧工地的产品,需要对工人进行一些数据统计,比如要统计导入人员数量小于30的工地,SQL应该怎么写呢? 首先了解一下数据结构,工地分三张表,四级层级关系,Organization表存储区域 ...

  5. layui上传同一张图片第二次时choose没有反应

    将上传文件的input的val设置为空 $("#test11").parent().find("input").val('');

  6. Java多线程_CAS算法和ABA问题

    CAS算法概述CAS是英文单词CompareAndSwap的缩写,中文意思是:比较并替换.CAS需要有3个操作数:内存地址V,旧的预期值A,即将要更新的目标值B. CAS指令执行时,当且仅当内存地址V ...

  7. vuex的模块化使用

    store文件如下 1.modules下文件是模块化的划分,里面的js有state,action,mutations.然后通过 export default { namespaced: true, s ...

  8. Ambari 邮件监控服务

    配置邮箱授权码 这里演示的是网易邮箱 开启SMTP服务 配置邮件模板 • 下载警告邮件模板 wget https://raw.githubusercontent.com/apache/ambari/b ...

  9. JavaScript基础语法资料

    JavaScript基础第01天 1 - 编程语言 1.1 编程 编程: 就是让计算机为解决某个问题而使用某种程序设计语言编写程序代码,并最终得到结果的过程. 计算机程序: 就是计算机所执行的一系列的 ...

  10. 如何解决 iframe 无法触发 clickOutside

    注:(1)非原创,来自https://blog.csdn.net/weixin_33985679/article/details/89699215.https://zhuanlan.zhihu.com ...