今天是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. C#LeetCode刷题-数学

    数学篇 # 题名 刷题 通过率 难度 2 两数相加   29.0% 中等 7 反转整数 C#LeetCode刷题之#7-反转整数(Reverse Integer) 28.6% 简单 8 字符串转整数 ...

  2. Flutter 容器Container类和布局Layout类

    1.布局和容器 [布局]是把[容器]按照不同的方式排列起来. Scaffold包含的主要部门:appBar,body,bottomNavigator 其中body可以是一个布局组件,也可以是一个容器组 ...

  3. .NET或.NET Core Web APi基于tus协议实现断点续传

    前言 前两天我采用技巧式方案基本实现大文件分片上传,这里只是重点在于个人思路和亲身实践,若在实际生产环境要求比较高的话肯定不行,仍存在一些问题需要深入处理,本文继续在之前基础上给出基于tus协议的轮子 ...

  4. vs2017引用vue组件中文乱码

    原因:文件默认编码格式为ASNI编码,需要改成UTF-8编码 解决方案: ①用记事本打开component.js文件 ②另存文件,修改编码为UTF-8编码,保存

  5. Mapreduce学习(一)

    MapReduce 介绍 简单介绍: MapReduce思想在生活中处处可见.或多或少都曾接触过这种思想.MapReduce的思想核心是“分而治之”,适用于大量复杂的任务处理场景(大规模数据处理场景) ...

  6. 二叉搜索树及java实现

    二叉搜索树(Binary Search Tree) 二叉搜索树是二叉树的一种,是应用非常广泛的一种二叉树,英文简称为 BST 又被称为:二叉查找树.二叉排序树 任意一个节点的值都大于其左子树所有节 ...

  7. openvswitch常用bond相关命令

    1.列出bondovs-appctl bond/list[root@test~]# ovs-appctl bond/listbond type recircID slavesbond1 balance ...

  8. redis安装及性能测试

    Redis是一个开源的使用ANSI C语言编写.遵守BSD协议.支持网络.可基于内存亦可持久化的日志型.Key-Value数据库.通常被称为数据结构服务器,因为值(value)可以是 字符串(Stri ...

  9. Reinforcement learning in artificial and biological systems

    郑重声明:原文参见标题,如有侵权,请联系作者,将会撤销发布! Abstract 在生物和人工系统的学习研究之间,已经有富有成果的概念和想法流.Bush and Mosteller,Rescorla a ...

  10. discuz论坛替换logo之后不显示该怎么办

    http://www.wocaoseo.com/thread-2-1-1.html 这个虽然不算是seo的问题,但是在平时的操作之中经常性的遇到,常常是我用FTP已经上传替换了原来的logo,但是前台 ...