锁
在多线程执行情况下,会存在对同一变量同时修改的情况;不能保证资源的修改后数据的一致(和我们期望)我们称为线程不安全。
这种情况,在编程语言中我们可以通过加锁解决。
我们先来看在不加锁的情况下,会产生的问题。
package main
import (
"fmt"
"sync"
)
var w sync.WaitGroup
type Account struct {
name string
amount int
}
// 取钱
func (a *Account) WithDraw(num int) {
a.amount -= num
}
// 存钱
func (a *Account) Deposit(num int) {
a.amount += num
}
func main() {
a := Account{
name: "zhangsan",
amount: 0, // 初始化金额为0
}
w.Add(2)
// 开一个gorounine 存钱
go func() {
defer w.Done()
for i := 0; i < 1000; i++ {
a.Deposit(100) // 每次存100
}
}()
// 开一个gorountine 取钱
go func() {
defer w.Done()
for i := 0; i < 1000; i++ {
a.WithDraw(50) // 每次取50
}
}()
w.Wait() // 等待所有gorountine执行完
fmt.Println(a.amount) // 最终金额
}
多次执行,我们会发现每次执行后打印的值都不一样,因为,并发情况下对amount的修改是非线程安全的。
下面我们开始修复它。
1. sync.Mutext
互斥锁
在修改amount金额时,先加锁;修改完释放锁;
代码如下:
package main
import (
"fmt"
"sync"
)
var (
lock sync.Mutex
w sync.WaitGroup
)
type Account struct {
name string
amount int
}
// 取钱
func (a *Account) WithDraw(num int) {
lock.Lock() // 加锁
a.amount -= num
lock.Unlock() //释放锁
}
// 存钱
func (a *Account) Deposit(num int) {
lock.Lock() // 加锁
a.amount += num
lock.Unlock() //释放锁
}
func main() {
a := Account{
name: "zhangsan",
amount: 0, // 初始化金额为0
}
w.Add(2)
// 开一个gorounine 存钱
go func() {
defer w.Done()
for i := 0; i < 1000; i++ {
a.Deposit(100) // 每次存100
}
}()
// 开一个gorountine 取钱
go func() {
defer w.Done()
for i := 0; i < 1000; i++ {
a.WithDraw(50) // 每次取50
}
}()
w.Wait() // 等待所有gorountine执行完
fmt.Println(a.amount) // 最终金额
}
现在执行代码每次输出都是50000,数据正确啦~
2. sync.RWMutex
读写锁
sync.Mutex
可以解决问题,但是在许多场景下,都是读多写少;在这种情况下,使用读写锁更好。
什么事读写锁呢?
- 在一个线程的对读加锁的情况,其它线程也可以读
- 只有在线程写的情况下,其它线程不能读和写
总结下就是,只有写的时候会造成阻塞,其它时候不会阻塞;因此非常时候读多写入。
其写法和互斥差不多,只是在读的时候,写法不同多了R
-
lock.RLock()
加读锁 -
lock.RUnlock()
释放读锁
我们来看看代码:
package main
import (
"fmt"
"sync"
"time"
)
var (
x = 0
lock sync.RWMutex // 读写锁
w sync.WaitGroup
)
// 读
func read() {
lock.RLock() // 读上锁
time.Sleep(5 * time.Millisecond) // 模拟耗时操作
lock.RUnlock() //读释放锁
}
// 写
func write() {
lock.Lock() // 写加锁 (和互斥锁写法一样)
x += 1
time.Sleep(50 * time.Millisecond) // 模拟耗时间操作
lock.Unlock()
}
func main() {
w.Add(2)
// 开一个gorounine 读(多)
go func() {
defer w.Done()
for i := 0; i < 1000; i++ {
read()
}
}()
// 开一个gorountine 写
go func() {
defer w.Done()
for i := 0; i < 10; i++ { // 写的少
write()
}
}()
w.Wait() // 等待所有gorountine执行完
fmt.Println(x) // 打印x值
}
网友评论