27、golang基础-互斥锁、读写锁

1、Golang的锁

Golang中的锁有两种:sync.Mutexsync.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)把计数器设置为nDone()每次把计数器-1wait()会阻塞代码的运行,直到计数器的值减为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为零,也就是所有的100for循环都运行完毕。相对于使用管道来说,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调用会从获得的锁中排除新的读取器
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值