这是我参与「第五届青训营」笔记创作活动的第4天。
一、内容:
通过前几天对Go并发编程的学习,了解到Go中的CSp主要有两种:
通过通信实现共享内存、通过共享内存实现通信
前面的笔记里有了对第一种CSP模型比较详细的记载,但没有展开讲述第二种CSP模型。故在今天更加充分的去学习了第二种CSP模型,以及Go中常见锁的相关知识。在此写下经过这段学习后我对第二种CSP模型--通过共享内存实现通信的理解
二、临界区
在go语言中,经常会遇到并发的问题,当然我们会优先考虑使用通道,同时go语言也提供了传统解决方式Mutex(互斥锁)和RWMutex(读写锁)来处理竞争问题。发生并发问题的原因在于并行操作了共同的资源,发生了资源竞争。这些修改公共资源的代码被称为临界区。
这里使用银行存款问题来演示:
type Bank struct {
balance int //余额
}
func (b *Bank) deposit(amount int, wg *sync.WaitGroup) {
defer wg.Done()
b.balance += amount
}
func (b *Bank) getBalance() int {
return b.balance
}
func main() {
b := new(Bank)
var wg sync.WaitGroup
wg.Add(1000)
for i := 0; i < 1000; i++ {
go b.deposit(1, &wg)
}
wg.Wait()
fmt.Println(b.getBalance())
}
//生成不同的结果 如 987
这里的balence就是临界区
三、互斥锁和读写锁
可以使用互斥锁或读写锁解决这个问题:
使用互斥锁来解决这个问题:
type Bank struct {
balance int //余额
m sync.Mutex
}
func (b *Bank) deposit(amount int, wg *sync.WaitGroup) {
defer wg.Done()
b.m.Lock()
b.balance += amount
b.m.Unlock()
}
但是,互斥锁会导致同一时间只有一个程序可以执行加锁后的程序,对于读操作较多的业务不是很友好,此时可以使用读写锁。
type Bank struct {
balance int //余额
m sync.RWMutex
}
func (b *Bank) deposit(amount int, wg *sync.WaitGroup) {
defer wg.Done()
defer b.m.Unlock()
b.m.Lock()
b.balance += amount
}
func (b *Bank) getBalance() (balance int) {
b.m.RLock()
balance = b.balance
b.m.RUnlock()
return //默认返回blance
}
我们将互斥锁改为读写锁,并改变读取函数,从而实现读写锁。
读写锁与互斥锁的区别:
Mutex 是最简单的一种锁类型,同时也比较暴力,当一个 goroutine 获得了 Mutex 后,其他 goroutine 就只能乖乖等到这个 goroutine 释放该 Mutex。
RWMutex--读写锁,顾名思义是一把锁分为两部分:读锁和写锁,其中读锁允许多个线程同时获得,因为读操作本身是线程安全的,而写锁则是互斥锁,不允许多个线程同时获得写锁,并且写操作和读操作也是互斥的。总结来说,读写锁的特点是:读读不互斥、读写互斥、写写互斥。
主要遵循以下规则 :
- 读写锁的读锁可以重入,在已经有读锁的情况下,可以任意加读锁。
- 在读锁没有全部解锁的情况下,写操作会阻塞直到所有读锁解锁。
- 写锁定的情况下,其他协程的读写都会被阻塞,直到写锁解锁。
四、总结:
今天通过这些例子对临界区、锁、共享内存实现通信有了进一步的理解。