golang 互斥锁和读写锁

1. 互斥锁

先看个例子

func main(){
	fmt.Println("start study golang...... ")

	// 1.声明互斥锁
    var mutex sync.Mutex
	count:=0;
	for r:=0;r<20 ;r++ {
		go func() {
			mutex.Lock()
			count+=1;
			defer mutex.Unlock();
		}()
		time.Sleep(time.Second)
		fmt.Println("the count is : ", count)
	}
}

每个资源都对应于一个可称为 “互斥锁” 的标记,这个标记用来保证在任意时刻,只能有一个协程(线程)访问该资源。其它的协程只能等待。

互斥锁是传统并发编程对共享资源进行访问控制的主要手段,它由标准库sync中的Mutex结构体类型表示。sync.Mutex类型只有两个公开的指针方法,Lock和Unlock。Lock锁定当前的共享资源,Unlock进行解锁。

在使用互斥锁时,一定要注意:对资源操作完成后,一定要解锁,否则会出现流程执行异常,死锁等问题。通常借助defer。锁定后,立即使用defer语句保证互斥锁及时解锁。如下所示:

var mutex sync.Mutex // 定义互斥锁变量 mutex

func write(){
  mutex.Lock( )
  defer mutex.Unlock( )
}


2. 读写锁

互斥锁的本质是当一个goroutine访问的时候,其他goroutine都不能访问。这样在资源同步,避免竞争的同时也降低了程序的并发性能。程序由原来的并行执行变成了串行执行。

其实,当我们对一个不会变化的数据只做“读”操作的话,是不存在资源竞争的问题的。因为数据是不变的,不管怎么读取,多少goroutine同时读取,都是可以的。

所以问题不是出在“读”上,主要是修改,也就是“写”。修改的数据要同步,这样其他goroutine才可以感知到。所以真正的互斥应该是读取和修改、修改和修改之间,读和读是没有互斥操作的必要的。

因此,衍生出另外一种锁,叫做读写锁。

读写锁可以让多个读操作并发,同时读取,但是对于写操作是完全互斥的。也就是说,当一个goroutine进行写操作的时候,其他goroutine既不能进行读操作,也不能进行写操作。

GO中的读写锁由结构体类型sync.RWMutex表示。此类型的方法集合中包含两对方法:

一组是对写操作的锁定和解锁,简称“写锁定”和“写解锁”:
func (*RWMutex)Lock()
func (*RWMutex)Unlock()

另一组表示对读操作的锁定和解锁,简称为“读锁定”与“读解锁”:
func (*RWMutex)RLock()
func (*RWMutex)RUnlock()

读写锁基本示例:
 

package main

import (
	"fmt"
	"math/rand"
	"sync"
	"time"
)

var count int

var mutex sync.RWMutex

func main(){
	fmt.Println("start study golang...... ")

	// 1.声明互斥锁
    //var mutex sync.Mutex
	//count:=0;
	//for r:=0;r<20 ;r++ {
	//	go func() {
	//		mutex.Lock()
	//		count+=1;
	//		defer mutex.Unlock();
	//	}()
	//	time.Sleep(time.Second)
	//	fmt.Println("the count is : ", count)
	//}



	for i := 0; i < 10; i++ {
		go read(i + 1)
	}

	for i := 0; i < 10; i++ {
		go write(i + 1)
	}


	time.Sleep(time.Second*5)
}
func write(n int) {
	rand.Seed(time.Now().UnixNano())
	fmt.Errorf("写 goroutine %d 正在写数据...\n", n)
	mutex.Lock()
	num := rand.Intn(500)
	count = num
	fmt.Printf("写 goroutine %d 写数据结束,写入新值 %d\n", n, num)
	mutex.Unlock()

}
func read(n int) {
	mutex.RLock()
	fmt.Printf("读 goroutine %d 正在读取数据...\n", n)
	num := count
	fmt.Printf("读 goroutine %d 读取数据结束,读到 %d\n", n, num)
	mutex.RUnlock()
}


我们在read里使用读锁,也就是RLock和RUnlock,写锁的方法名和我们平时使用的一样,是Lock和Unlock。这样,我们就使用了读写锁,可以并发地读,但是同时只能有一个写,并且写的时候不能进行读操作。

我们从结果可以看出,读取操作可以并行,例如2,3,1正在读取,但是同时只能有一个写,例如1正在写,只能等待1写完,这个过程中不允许进行其它的操作。

处于读锁定状态,那么针对它的写锁定操作将永远不会成功,且相应的Goroutine也会被一直阻塞。因为它们是互斥的。
 

已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 博客之星2020 设计师:CY__ 返回首页