解析golang中的并发安全和锁问题

1. 并发安全

package main

import (
    "fmt"
    "sync"
)

var (
    sum int
    wg sync.WaitGroup
)

func test() {
    for i := 0; i < 5000000; i++ {
        sum += 1
    }
    wg.Done()
}

func main() {
    // 并发和安全锁
    wg.Add(2)
    go test()
    go test()

    wg.Wait()
    fmt.Println(sum)

}

  上面的代码中我们开启了两个goroutine去累加变量x的值,这两个goroutine在访问和修改x变量的时候就会存在数据竞争,导致最后的结果与期待的不符。

2. 互斥锁

package main

import (
    "fmt"
    "sync"
)

var (
    sum int
    wg sync.WaitGroup
    mu sync.Mutex  // 定义一个互斥锁
)

func test() {
    for i := 0; i < 10000000; i++ {
        // 互斥锁它能够保证同时只能有一个goroutine去访问共享资源
        mu.Lock()
        sum += 1
        mu.Unlock()
    }
    wg.Done()
}

func main() {
    fmt.Println(mu)
    // 并发和安全锁
    wg.Add(2)
    go test()
    go test()

    wg.Wait()
    fmt.Println(sum)

}

  使用互斥锁能够保证同一时间有且只有一个goroutine进入临界区,其他的goroutine则在等待锁;当互斥锁释放后,等待的goroutine才可以获取锁进入临界区,多个goroutine同时等待一个锁时,唤醒的策略是随机的。

3. 读写互斥锁

互斥锁是完全互斥的,但是有很多实际的场景下是读多写少的,当我们并发的去读取一个资源不涉及资源修改的时候是没有必要加锁的,这种场景下使用读写锁是更好的一种选择。读写锁在Go语言中使用sync包中的RWMutex类型。

读写锁分为两种:读锁和写锁。当一个goroutine获取读锁之后,其他的goroutine如果是获取读锁会继续获得锁,如果是获取写锁就会等待;当一个goroutine获取写锁之后,其他的goroutine无论是获取读锁还是写锁都会等待。

package main

import (
    "fmt"
    "sync"
    "time"
)

var (
    x int
    wg sync.WaitGroup
    mu sync.Mutex  // 定义一个互斥锁
    rw sync.RWMutex  // 定义一个读写锁,注意:只有读多写少的时候,读写锁才能发挥其优势
)

func write() {
    rw.Lock()
    x += 1
    time.Sleep(10 * time.Millisecond)  // 假设写入时间耗费10毫秒
    rw.Unlock()
    wg.Done()
}
func read() {
    rw.RLock()
    time.Sleep(time.Millisecond)
    rw.RUnlock()
    wg.Done()
}

func main() {
    start := time.Now()
    for i := 0; i < 10; i++ {
        wg.Add(1)
        go write()
    }  // 写耗时:160毫秒左右

    for i := 0; i < 1000; i++ {
        wg.Add(1)
        go read()
    }  // 读耗时:15毫秒左右

    wg.Wait()
    end := time.Now()
    fmt.Println("执行时间:", end.Sub(start))
}

  需要注意的是读写锁非常适合读多写少的场景,如果读和写的操作差别不大,读写锁的优势就发挥不出来。

到此这篇关于golang中的并发安全和锁的文章就介绍到这了,更多相关golang并发和锁内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Golang并发操作中常见的读写锁详析

    互斥锁简单粗暴,谁拿到谁操作.今天给大家介绍一下读写锁,读写锁比互斥锁略微复杂一些,不过我相信我们今天能够把他拿下! golang读写锁,其特征在于 读锁:可以同时进行多个协程读操作,不允许写操作 写锁:只允许同时有一个协程进行写操作,不允许其他写操作和读操作 读写锁有两种模式.没错!一种是读模式,一种是写模式.当他为写模式的话,作用和互斥锁差不多,只允许有一个协程抢到这把锁,其他协程乖乖排队.但是读模式就不一样了,他允许你多个协程读,但是不能写.总结起来就是: 仅读模式: 多协程可读不可写 仅

  • 详解Golang并发操作中常见的死锁情形

    目录 第一种情形:无缓存能力的管道,自己写完自己读 第二种情形:协程来晚了 第三种情形:管道读写时,相互要求对方先读/写 第四种情形:读写锁相互阻塞,形成隐形死锁 什么是死锁,在Go的协程里面死锁通常就是永久阻塞了,你拿着我的东西,要我先给你然后再给我,我拿着你的东西又让你先给我,不然就不给你.我俩都这么想,这事就解决不了了. 第一种情形:无缓存能力的管道,自己写完自己读 先上代码: func main() { ch := make(chan int, 0) ​ ch <- 666 x := <

  • golang 并发安全Map以及分段锁的实现方法

    涉及概念 并发安全Map 分段锁 sync.Map CAS ( Compare And Swap ) 双检查 分断锁 type SimpleCache struct { mu sync.RWMutex items map[interface{}]*simpleItem } 在日常开发中, 上述这种数据结构肯定不少见,因为golang的原生map是非并发安全的,所以为了保证map的并发安全,最简单的方式就是给map加锁. 之前使用过两个本地内存缓存的开源库, gcache, cache2go,其中

  • 解析golang中的并发安全和锁问题

    1. 并发安全 package main import ( "fmt" "sync" ) var ( sum int wg sync.WaitGroup ) func test() { for i := 0; i < 5000000; i++ { sum += 1 } wg.Done() } func main() { // 并发和安全锁 wg.Add(2) go test() go test() wg.Wait() fmt.Println(sum) } 上面

  • 解析Golang中的GoPath和GoModule

    目录 什么是GoPath? 什么是GoModule? GoModule的设置 GoModule无法下载国外的依赖包问题在Golang中,有两个概念非常容易弄错,第一个就是GoPath,第二个则是GoModule,很多初学者不清楚这两者之间的关系,也就难以清晰地了解项目的整体结构,自然也就难以编写结构清晰的代码. 什么是GoPath? 什么是Gopath?在我的上一篇博客Golang环境安装&IDEA开发Golang中,曾经提到过GoPath的概念.GoPath是Golang的工作空间,所有的Go

  • 解析Golang中引用类型是否进行引用传递

    目录 引言 引用类型 引用变量(reference variable)和引用传递(pass-by-reference) Golang是否存在引用变量(reference variable) 字典为什么可以做到值传递但是可以更改原对象? 结语 引言 开篇明义,Go lang中从来就不存在所谓的“引用传递”,从来就只有一种变量传递方式,那就是值传递.因为引用传递的前提是存在“引用变量”,但是Go lang中从来就没有出现过所谓的“引用变量”,所以也就不可能存在引用传递这种变量传递的方式. 引用类型

  • 一文带你了解Golang中的并发性

    目录 什么是并发性,为什么它很重要 并发性与平行性 Goroutines, the worker Mortys Channels, the green portal 总结 并发是一个很酷的话题,一旦你掌握了它,就会成为一笔巨大的财富.说实话,我一开始很害怕写这篇文章,因为我自己直到最近才对并发性不太适应.我已经掌握了基础知识,所以我想帮助其他初学者学习Go的并发性.这是众多并发性教程中的第一篇,请继续关注更多的教程. 什么是并发性,为什么它很重要 并发是指在同一时间运行多个事物的能力.你的电脑有

  • golang中的并发和并行

    golang中默认使用一个CPU,这时程序无法并发,只能是并发.因为始终只有一个CPU在运行. package main import ( "fmt" "runtime" ) //并发和并行 var quit chan int = make(chan int) func loop() { for i := 0; i < 100; i++ { //为了观察,跑多些 fmt.Printf("%d ", i) } quit <- 0 } f

  • 深入解析golang中的标准库flag

    Go语言内置的flag包实现了命令行参数的解析,flag包使得开发命令行工具更为简单. os.Args 如果你只是简单的想要获取命令行参数,可以像下面的代码示例一样使用os.Args来获取命令行参数. func main() { // 获取命令行参数 // os.Args:[]string if len(os.Args) > 0 { for i, v := range os.Args { fmt.Println(i, v) } } } 执行命令:go run .\main.go host:127

  • 详解Golang 中的并发限制与超时控制

    前言 上回在 用 Go 写一个轻量级的 ssh 批量操作工具里提及过,我们做 Golang 并发的时候要对并发进行限制,对 goroutine 的执行要有超时控制.那会没有细说,这里展开讨论一下. 以下示例代码全部可以直接在 The Go Playground上运行测试: 并发 我们先来跑一个简单的并发看看 package main import ( "fmt" "time" ) func run(task_id, sleeptime int, ch chan st

  • Go-ethereum 解析ethersjs中产生的签名信息思路详解

    目录 Go-ethereum 解析ethersjs中产生的签名信息 1. ethers 消息签名 1.1 ethers 对签名消息进行解析 2. Golang 签名解析 3. 总结 Go-ethereum 解析ethersjs中产生的签名信息 在签名验证的过程中,我们判断签名正确的前提是,签名解析后的公钥,和发起这次动作的人是同一个公钥.我们解析签名的需要知道,签名的消息,签名,和公钥. 按照这个思路,我们可以通过ethers实现消息的签名,也可以通过go-ethereum实现. 在签名的解析过

  • Java多线程并发编程和锁原理解析

    这篇文章主要介绍了Java多线程并发编程和锁原理解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.前言 最近项目遇到多线程并发的情景(并发抢单&恢复库存并行),代码在正常情况下运行没有什么问题,在高并发压测下会出现:库存超发/总库存与sku库存对不上等各种问题. 在运用了 限流/加锁等方案后,问题得到解决. 加锁方案见下文. 二.乐观锁 & 悲观锁 1.乐观锁 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁

  • golang中cache组件的使用及groupcache源码解析

    groupcache 简介 在软件系统中使用缓存,可以降低系统响应时间,提高用户体验,降低某些系统模块的压力. groupcache是一款开源的缓存组件.与memcache与redis不同的时,groupcache不需要单独的部署,可以作为你程序的一个库来使用. 这样方便我们开发的程序部署. 本篇主要解析groupcache源码中的关键部分, lru的定义以及如何做到同一个key只加载一次. 缓存填充以及加载抑制的实现 上篇有提到load函数的实现, 缓存填充的逻辑也体现在这里. groupca

随机推荐