本文将详细讲解Go语言中什么是互斥,文章内容质量较高,所以分享给大家作为参考。希望大家看完这篇文章后对相关知识有一定的了解。
00-1010
1、互斥锁Mutex
go语言同步工具主要由sync包提供,Mutex和RWMutex是sync包中的方法。
互斥锁可以用来保护一个关键区域,并确保同时只有一个goroutine在该关键区域。主要包括锁定(锁定方式)和解锁(解锁方式)两种操作。首先,它锁定进入关键区域的goroutine,并在它离开时解锁。
使用互斥锁 (Mutex)时要注意以下几点:
不要重复锁定互斥,否则会阻塞,还可能导致死锁。
要解锁互斥,这也是为了避免重复锁定;
不要解锁已解锁或未锁定的互斥体;
不要在多个函数之间直接传递互斥。同步。互斥类型是值类型。当它被传递给函数时,将生成一个副本。函数中锁的操作不会影响原来的锁。
总之,互斥体只是用来保护一个关键区域,锁定后记得解锁。对于每个锁定操作,必须只有一个对应的解锁操作,即锁定和解锁应该成对出现,最安全的方式是使用delay语句将其解锁。
1.1 Mutex介绍
下面的代码模拟取钱和存钱操作:
packagemain
导入(
'旗帜'
fmt '
同步
)
var(
mutexsync。互斥(体)…
平衡
是否固定uint//锁?
Sign=make(chanstruct{},10)//channel,用于等待所有goroutine。
)
//省钱。
func deposit(value int){ 0
defer func(){ 0
符号结构{}{}
}()
ifprotecting==1 {
互斥体。锁定()
延期互斥。解锁()
}
Fmt。Printf('余额:%d\n ',余额)。
余额=价值
Fmt。Printf('保存%d后的余额为:%d\n ',值,余额)。
fmt。Println()
}
//取钱。
functure(value int){ 0
defer func(){ 0
符号结构{}{}
}()
如果
nbsp;protecting == 1 {
mutex.Lock()
defer mutex.Unlock()
}
fmt.Printf("余额: %d\n", balance)
balance -= value
fmt.Printf("取 %d 后的余额: %d\n", value, balance)
fmt.Println()
}
func main() {
for i:=0; i < 5; i++ {
go withdraw(500) // 取500
go deposit(500) // 存500
}
for i := 0; i < 10; i++ {
<-sign
}
fmt.Printf("当前余额: %d\n", balance)
}
func init() {
balance = 1000 // 初始账户余额为1000
flag.UintVar(&protecting, "protecting", 0, "是否加锁,0表示不加锁,1表示加锁")
}
上面的代码中,使用了通道来让主 goroutine 等待其他 goroutine 运行结束,每个子goroutine在运行结束之前向通道发送一个元素,主
goroutine
在最后从这个通道接收元素,接收次数与子goroutine个数相同。接收完后就会退出主goroutine。
代码使用协程实现多次(5次)对一个账户进行存钱和取钱的操作,先来看不加锁的情况
余额: 1000
存 500 后的余额: 1500余额: 1000
取 500 后的余额: 1000余额: 1000
存 500 后的余额: 1500余额: 1000
取 500 后的余额: 1000余额: 1000
存 500 后的余额: 1500余额: 1000
取 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 1000
存 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 1000
存 500 后的余额: 1000当前余额: 1000
可以看到出现了混乱,比如第二次1000的余额取500后还是1000,这种对同一资源的竞争出现了竞态条件(Race Condition)。
下面来看加锁的执行结果:
余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 1000余额: 1000
存 500 后的余额: 1500余额: 1500
取 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 1000当前余额: 1000
加锁后就正常了。
下面介绍更细化的互斥锁:读/写互斥锁RWMutex
。
2、读写锁RWMutex
2.1 RWMutex介绍
读/写互斥锁RWMutex
包含了读锁和写锁,分别对共享资源的“读操作”和“写操作”进行保护。sync.RWMutex
类型中的Lock
方法和Unlock
方法分别用于对写锁进行锁定和解锁,而它的RLock
方法和RUnlock
方法则分别用于对读锁进行锁定和解锁。
有了互斥锁Mutex
,为什么还需要读写锁呢?因为在很多并发操作中,并发读取占比很大,写操作相对较少,读写锁可以并发读取,这样可以提供服务性能。读写锁具有以下特征:
读写锁 | 读锁 | 写锁 |
---|---|---|
读锁 | Yes | No |
写锁 | No | No |
也就是说:
-
如果某个共享资源受到读锁和写锁保护时,其它
goroutine
不能进行写操作。换句话说就是读写操作和写写操作不能并行执行,也就是读写互斥; -
受读锁保护时,可以同时进行多个读操作。
在使用读写锁时,还需要注意:
-
不要对未锁定的读写锁解锁;
-
对读锁不能使用写锁解锁
-
对写锁不能使用读锁解锁
2.2 RWMutex使用实例
改写前面的取钱和存钱操作,添加查询余额的方法:
package main import ( "fmt" "sync" ) // account 代表计数器。 type account struct { num uint // 操作次数 balance int // 余额 rwMu *sync.RWMutex // 读写锁 } var sign = make(chan struct{}, 15) //通道,用于等待所有goroutine // 查看余额:使用读锁 func (c *account) check() { defer func() { sign <- struct{}{} }() c.rwMu.RLock() defer c.rwMu.RUnlock() fmt.Printf("%d 次操作后的余额: %d\n", c.num, c.balance) } // 存钱:写锁 func (c *account) deposit(value int) { defer func() { sign <- struct{}{} }() c.rwMu.Lock() defer c.rwMu.Unlock() fmt.Printf("余额: %d\n", c.balance) c.num += 1 c.balance += value fmt.Printf("存 %d 后的余额: %d\n", value, c.balance) fmt.Println() } // 取钱:写锁 func (c *account) withdraw(value int) { defer func() { sign <- struct{}{} }() c.rwMu.Lock() defer c.rwMu.Unlock() fmt.Printf("余额: %d\n", c.balance) c.num += 1 c.balance -= value fmt.Printf("取 %d 后的余额: %d\n", value, c.balance) fmt.Println() } func main() { c := account{0, 1000, new(sync.RWMutex)} for i:=0; i < 5; i++ { go c.withdraw(500) // 取500 go c.deposit(500) // 存500 go c.check() } for i := 0; i < 15; i++ { <-sign } fmt.Printf("%d 次操作后的余额: %d\n", c.num, c.balance) }
执行结果:
余额: 1000
取 500 后的余额: 5001 次操作后的余额: 500
1 次操作后的余额: 500
1 次操作后的余额: 500
1 次操作后的余额: 500
1 次操作后的余额: 500
余额: 500
存 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 1000余额: 1000
存 500 后的余额: 1500余额: 1500
取 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 1000余额: 1000
取 500 后的余额: 500余额: 500
存 500 后的余额: 100010 次操作后的余额: 1000
读写锁和互斥锁的不同之处在于读写锁把对共享资源的读操作和写操作分开了,可以实现更复杂的访问控制。
读写锁也是一种互斥锁,它是互斥锁的扩展。在使用时需要注意:
-
加锁后一定要解锁
-
不要重复加锁或者解锁
-
不解锁未锁定的锁
-
不要传递互斥锁
关于Go语言中什么是互斥锁就分享到这里了,希望
内容来源网络,如有侵权,联系删除,本文地址:https://www.230890.com/zhan/40192.html