脚本之家,脚本语言编程技术及教程分享平台!
分类导航

Python|VBS|Ruby|Lua|perl|VBA|Golang|PowerShell|Erlang|autoit|Dos|bat|shell|

服务器之家 - 脚本之家 - Golang - Go并发与锁的两种方式该如何提效详解

Go并发与锁的两种方式该如何提效详解

2022-12-28 15:36机智的程序员小熊 Golang

如果没有锁,在我们的项目中,可能会存在多个goroutine同时操作一个资源(临界区),这种情况会发生竞态问题(数据竞态),下面这篇文章主要给大家介绍了关于Go并发与锁的两种方式该如何提效的相关资料,需要的朋友可以参考下

并发安全,就是多个并发体在同一段时间内访问同一个共享数据,共享数据能被正确处理。

很多语言的并发编程很容易在同时修改某个变量的时候,因为操作不是原子的,而出现错误计算,比如一个加法运算使用中的变量被修改,而导致计算结果出错,典型的像统计商品库存。

个人建议只要涉及到共享变量统统使用channel,因为channel源码中使用了互斥锁,它是并发安全的。

我们可以不用,但不可以不了解,手中有粮心中不慌。

并发不安全的例子

数组是并发不安全的,在例子开始前我们要知道append函数的行为:长度足够在原数组cap内追加函数,增加len,长度不够则触发扩容,申请新数组cap增加一倍,赋值迁移。

所以在这个过程中,仅讨论扩容操作的话可能存在同时申请并赋值的情况,导致漏掉某次扩容增加的数据。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
var s []int
 
func appendValue(i int) {
    s = append(s, i)
}
 
func main() {
    for i := 0; i < 10000; i++ { //10000个协程同时添加切片
        go appendValue(i)
    }
    time.Sleep(2)
    fmt.Println(len(s))
}

比如上例,10000 个协程同时为切片增加数据,你可以尝试运行一下,打印出来的一定不是 10000

  • 以上并发操作的同一个资源,专业名词叫做临界区
  • 因为并发操作存在数据竞争,导致数据值意外改写,最后的结果与期待的不符,这种问题统称为竞态问题

常见于控制商品减库存,控制余额增减等情况。 那么有什么办法解决竞态问题呢?

  • 互斥锁:让访问某个临界区的时候,只有一个 goroutine 可以访问。
  • 原子操作:让某些操作变成原子的,这个后续讨论。

这两个思路贯穿了无数的高并发/分布式方案,区别是在一个进程应用中使用,还是借助某些第三方手段来实现,比如中间件。独孤九剑森罗万象一定要牢牢记住。

互斥锁

Go 语言中互斥锁的用法如下:

?
1
2
3
4
var lock sync.Mutex //互斥锁
lock.Lock() //加锁
s = append(s, i)
lock.Unlock() //解锁

在访问临界区的前后加上互斥锁,就可以保证不会出现并发问题。

我们修改还是上一个4.7.1的例子,为其增加互斥锁。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
var s []int
var lock sync.Mutex
 
appendValueSafe := func(i int) {
    lock.Lock()
    s = append(s, i)
    lock.Unlock()
}
 
for i := 0; i < 10000; i++ { //10000个协程同时添加切片
    go appendValueSafe(i)
}
time.Sleep(2)
fmt.Println(len(s))
  • 对共享变量s的写入操作加互斥锁,保证同一时刻只有一个goroutine修改内容。
  • 加锁之后到解锁之前的内容,同一时刻只有一个访问,无论读写。
  • 无论多少次都输出10000,不会再出现竞态问题。
  • 要注意:如果在写的同时,有并发读操作时,为了防止不要读取到写了一半数据,需要为读操作也加锁。

读写锁

互斥锁是完全互斥的,并发读没有修改的情况下是不会有问题的,也没有必要在并发读的时候加锁不然效率会变低。

用法:

?
1
2
3
4
5
6
7
8
rwlock sync.RWMutex
//读锁
rwlock.RLock()
rwlock.RUnlock()
 
//写锁
rwlock.Lock()
rwlock.Unlock()

并发读不互斥可以同时,在一个写锁获取时,其他所有锁都等待, 口诀:读读不互斥、读写互斥、写写互斥。具体测算速度的代码可以见4.7.3的源码,感兴趣的可以改下注释位置看下效率是有很明显的提升的。

小结

  • 学习了几个名词:临界区、竞态问题、互斥锁、原子操作、读写锁。
  • 互斥锁:sync.Mutex, 读写锁:sync.RWMutex 都是 sync 包的。
  • 读写锁比互斥锁效率高。

问题:只加写锁可以吗?为什么?

总结

到此这篇关于Go并发与锁的两种方式该如何提效的文章就介绍到这了,更多相关Go并发与锁提效内容请搜索服务器之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持服务器之家!

原文链接:https://juejin.cn/post/7181490237439213629

延伸 · 阅读

精彩推荐
  • GolangWindows环境下vscode-go安装笔记(不支持32位)

    Windows环境下vscode-go安装笔记(不支持32位)

    这篇文章主要介绍了Windows环境下vscode-go安装笔记(不支持32位),需要的朋友可以参考下 ...

    脚本之家7292020-05-05
  • Golanggolang映射Map的方法步骤

    golang映射Map的方法步骤

    这篇文章主要介绍了golang映射Map的方法步骤,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着...

    binbin11342021-01-02
  • GolangGolang 语言怎么避免空指针引发的 Panic

    Golang 语言怎么避免空指针引发的 Panic

    本文我们介绍一些 Golang 语言开发需要避免空指针引发 panic 的场景,虽然都比较简单,但是新手很容易踩“坑”。欢迎读者朋友们在评论区与大家分享更多...

    Golang语言开发栈11282022-01-05
  • Golanggolang db事务的统一封装的实现

    golang db事务的统一封装的实现

    这篇文章主要介绍了golang db事务的统一封装的实现,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    yuchenfw4392022-01-22
  • GolangGo语言HttpRouter路由使用方法详解

    Go语言HttpRouter路由使用方法详解

    这篇文章主要介绍了Go语言HttpRouter路由使用方法详解,需要的朋友可以参考下...

    骏马金龙10922022-09-24
  • Golang关于Golang中for-loop与goroutine的问题详解

    关于Golang中for-loop与goroutine的问题详解

    这篇文章主要给大家介绍了关于Golang中for-loop与goroutine问题的相关资料,文中通过示例代码介绍的非常详细,对大家学习或者使用golang具有一定的参考学习...

    寇池滨2822020-05-08
  • GolangGo语言 go程释放操作(退出/销毁)

    Go语言 go程释放操作(退出/销毁)

    这篇文章主要介绍了Go语言 go程释放操作(退出/销毁),具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    cqu_jiangzhou10912021-06-11
  • GolangGo语言fmt.Sprintf格式化输出的语法与实例

    Go语言fmt.Sprintf格式化输出的语法与实例

    Go 可以使用 fmt.Sprintf 来格式化字符串,下面这篇文章主要给大家介绍了关于Go语言fmt.Sprintf格式化输出的语法与实例,文中通过实例代码介绍的非常详细,需要...

    Hi丶ImViper6922022-07-29