go并发与锁

简介: go并发与锁

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


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


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


并发不安全的例子


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



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

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 语言中互斥锁的用法如下:

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

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

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


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,不会再出现竞态问题。

要注意:如果在写的同时,有并发读操作时,为了防止不要读取到写了一半数据,需要为读操作也加锁。


读写锁


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


用法:

rwlock sync.RWMutex
//读锁
rwlock.RLock()
rwlock.RUnlock()
//写锁
rwlock.Lock()
rwlock.Unlock()


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


小结


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


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

相关文章
|
9天前
|
并行计算 安全 Go
Go语言的并发特性
【10月更文挑战第26天】Go语言的并发特性
5 1
|
14天前
|
Java 大数据 Go
Go语言:高效并发的编程新星
【10月更文挑战第21】Go语言:高效并发的编程新星
40 7
|
19天前
|
安全 Go 调度
探索Go语言的并发模式:协程与通道的协同作用
Go语言以其并发能力闻名于世,而协程(goroutine)和通道(channel)是实现并发的两大利器。本文将深入了解Go语言中协程的轻量级特性,探讨如何利用通道进行协程间的安全通信,并通过实际案例演示如何将这两者结合起来,构建高效且可靠的并发系统。
|
19天前
|
安全 Go 开发者
破译Go语言中的并发模式:从入门到精通
在这篇技术性文章中,我们将跳过常规的摘要模式,直接带你进入Go语言的并发世界。你将不会看到枯燥的介绍,而是一段代码的旅程,从Go的并发基础构建块(goroutine和channel)开始,到高级模式的实践应用,我们共同探索如何高效地使用Go来处理并发任务。准备好,让Go带你飞。
|
20天前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel
在这个快节奏的技术时代,Go语言以其简洁的语法和强大的并发能力脱颖而出。本文将带你深入Go语言的并发机制,探索goroutine的轻量级特性和channel的同步通信能力,让你在高并发场景下也能游刃有余。
|
17天前
|
安全 程序员 Go
深入浅出Go语言的并发之道
在本文中,我们将探索Go语言如何优雅地处理并发编程。通过对比传统多线程模型,我们将揭示Go语言独特的goroutine和channel机制是如何简化并发编程,并提高程序的效率和稳定性。本文不涉及复杂的技术术语,而是用通俗易懂的语言,结合生动的比喻,让读者能够轻松理解Go语言并发编程的核心概念。
|
2月前
|
存储 安全 Go
Go to Learn Go之并发
Go to Learn Go之并发
29 8
|
2月前
|
Go 开发者
探索Go语言的并发之美
在Go语言的世界里,"并发"不仅仅是一个特性,它是一种哲学。本文将带你领略Go语言中goroutine和channel的魔力,揭示如何通过Go的并发机制来构建高效、可靠的系统。我们将通过一个简单的示例,展示如何利用Go的并发特性来解决实际问题,让你的程序像Go一样,轻盈而强大。
|
29天前
|
安全 Go 调度
探索Go语言的并发之美:goroutine与channel的实践指南
在本文中,我们将深入探讨Go语言的并发机制,特别是goroutine和channel的使用。通过实际的代码示例,我们将展示如何利用这些工具来构建高效、可扩展的并发程序。我们将讨论goroutine的轻量级特性,channel的同步通信能力,以及它们如何共同简化并发编程的复杂性。
|
30天前
|
安全 Go 数据处理
掌握Go语言并发:从goroutine到channel
在Go语言的世界中,goroutine和channel是构建高效并发程序的基石。本文将带你一探Go语言并发机制的奥秘,从基础的goroutine创建到channel的同步通信,让你在并发编程的道路上更进一步。