golang基础-锁
1、Golang的锁
Golang中的锁有两种:sync.Mutex
和sync.RWMutex
-
sync.Mutex
的锁只有一种锁:Lock()
,它是绝对锁,同一时间只能有一个锁 -
sync.RWMutex
叫读写锁,它有两种锁:Rlock()
和Lock()
RLock()叫读锁。它不是绝对锁,可以有多个读者同时获取此锁 Lock()叫写锁,它是个绝对锁,一旦某人拿了这个锁,别人就不能再获取此锁了。
2、sync.WaitGroup
https://blog.csdn.net/u013474436/article/details/88749749
经常会看到如下代码
func main(){
for i := 0; i < 100; i++ {
go fmt.Println(i)
}
time.Sleep(time.Second)
}
主线程为了等待goroutine都运行完毕,不得不带程序结尾使用time.Sleep()
来睡眠一段时间,等待其他线程充分运行。对于简单的代码100个循环可以在1秒内执行完毕,time.Sleep()
可以达到预期效果
但是对于实际开发场景,1秒是不够的,且大部分时候我们无法预知for循环内代码运行时长。这个时候就不能使用time.Sleep()
来完成等待操作了。
可以考虑使用管道来完成上述操作:
func main(){
c := make(chan bool, 100)
for i := 0; i < 100; i ++ {
go func(num int){
fmt.Println(num)
c <- true
}(i)
}
for i := 0; i < 100; i++ {
<- c
}
}
首先可以肯定的是使用管道可以达到我们的目的,且不仅能达到,还能十分完美的达到目的
但是管道在这里显得有些大材小用了,因为它被设计出来不仅仅只是在这里用作同步处理,在这里使用管道实际上是不合适的。而且假设我们有一万、十万甚至更多的for循环,也要申请同样数量大小的管道出来,对内存也是不小的开销。
对于这种情况,go语言中有一个其他的工具sync.WaitGroup
能更方便的帮助我们达到这个目的。
WaitGroup
对象内部有一个计数器,最初从0开始,它有三个方法:Add(),Done(),Wait()
用来控制计数器的数量。Add(n)
把计数器设置为n
,Done()
每次把计数器-1
,wait()
会阻塞代码的运行,直到计数器的值减为0
。
使用WaitGroup
将上述代码可以修改为:
func main(){
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i++{
go func(num int){
fmt.Println(i)
wg.Done()
}(i)
}
}
这里首先把wg计数器设置为100
,每个for
循环运行完毕都把计数器减1
,主函数中使用Wait()
一直阻塞,直到wg
为零,也就是所有的100
个for
循环都运行完毕。相对于使用管道来说,WaitGroup
轻巧了许多。
注意事项
1.计数器不能设置为负值
我们不能使用Add()
给wg
设置一个负值,否则代码将会报错:
panic: sync: negative WaitGroup counter
goroutine 1 [running]:
sync.(*WaitGroup).Add(0xc042008230, 0xffffffffffffff9c)
D:/Go/src/sync/waitgroup.go:75 +0x1d0
main.main()
D:/code/go/src/test-src/2-Package/sync/waitgroup/main.go:10 +0x54
同样使用Done()
也要特别注意不要把计数器设置成负数了。
2.WaitGroup对象不是一个引用类型
func main(){
wg := sync.WaitGroup{}
wg.Add(100)
for i := 0; i < 100; i ++ {
go f(i, &wg)
}
wg.Wait()
}
// 一定要通过指针传值,不然进程会进入死锁状态
func f(i int, wg *sync.WaitGroup){
fmt.Println(i)
wg.Done()
}
3、sync.Mutex
Mutex为互斥锁,Lock()加锁,Unlock()解锁,使用Lock()加锁后,不能再次对其进行加锁,直到利用Unlock()解锁后才能再次加锁,适用于读写不确定的场景,即读写次数没有明显的区别,并且只允许一个读或者写的场景,所以该锁也叫全局锁
已经锁定的Mutex并不与特定的goroutine关联,这样可以利用一个goroutine加锁,再利用其它goroutine对其解锁
package main
import (
"fmt"
"runtime"
"sync"
)
type Counter struct {
mu sync.Mutex
x int64
}
func (c *Counter) Inc() {
c.mu.Lock()
defer c.mu.Unlock()
c.x++
}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
c := Counter{}
var wait sync.WaitGroup
wait.Add(4)
for k := 4; k > 0; k-- {
go func() {
for i := 2500000; i > 0; i-- {
c.Inc()
}
wait.Done()
}()
}
wait.Wait()
fmt.Println(c.x)
}
4、sync.RWMutex
RWMutex是一个读写锁,该锁可以加多个读锁或者一个写锁,其经常用于读次数远远多于写次数的场景
- func(rw *RWMutex) Lock() 写锁,如果在添加写锁之前已经有其他的读锁和写锁,则lock就会阻塞直到该锁可用,已阻塞的Lock调用会从获得的锁中排除新的读取器