学习过操作系统的都知道程序有临界区这个概念,临界区就是程序片段访问临界资源的那部分代码,临界区同一时刻只能有一个线程进行访问,其他线程需要访问的话必须等待资源空闲。那么一般编程语言都会使用锁来进行临界区访问控制。

golang主要有两种锁:互斥锁读写锁

互斥锁Mutex:

Mutex 用于提供一种加锁机制(Locking Mechanism),保证同一时刻只有一个goroutine在临界区运行。

互斥锁定义如下:

var mutex sync.Mutex;

它用于对goroutine进行加锁和解锁,Lock()之后其他goroutine便不能对其锁定的区进行操作。

看下面代码:

package main

import (
	"fmt"
	"time"
)

func main() {
	x := 0
	go func() {
		x = x + 1
	}()
	time.Sleep(time.Second)
	fmt.Println(x)
}

一般它都会打印1。

但是有没有这种可能:

并发执行过程中出现时间延误,例如我goroutine1执行完后x=1,goroutine2拿到的x并不是0,而是goroutine1已经执行好的x = 1,那么x再次自加,就会返回2。

这就是由于多个goroutine对x进行操作之后可能产生的错误,没有进行临界区访问控制。

加锁:

package main

import (
	"fmt"
	"sync"
	"time"
)

func main() {
	var mutex sync.Mutex
	x := 0
	go func() {
		mutex.Lock()
		x = x + 1
		mutex.Unlock()
	}()
	time.Sleep(time.Second)
	fmt.Println(x)
}

这样的话就是线程安全了,只有一个goroutine对x进行操作。

读写锁RWMutex:

读写锁实际是一种特殊的自旋锁,它把对共享资源的访问者划分成读者和写者,读者只对共享资源进行读访问,写者则需要对共享资源进行写操作。这种锁相对于自旋锁而言,能提高并发性,因为在多处理器系统中,它允许同时有多个读者来访问共享资源,最大可能的读者数为实际的逻辑CPU数。写者是排他性的,一个读写锁同时只能有一个写者或多个读者(与CPU数相关),但不能同时既有读者又有写者。

总的来说,就是写的时候不允许读和多个写,读的时候不允许写但是允许多个读,读和写不能同时进行。

func (*RWMutex) Lock
func (*RWMutex) Unlock

func (*RWMutex) RLock
func (*RWMutex) RUnlock 

Lock方法将rw锁定为写入状态,禁止其他线程读取或者写入。

Unlock方法解除rw的写入锁状态,如果m未加写入锁会导致运行时错误。

RLock方法将rw锁定为读取状态,禁止其他线程写入,但不禁止读取。

RUnlock方法解除rw的读取锁状态,如果m未加读取锁会导致运行时错误。

func read() {
	rwmutex.RLock()
	fmt.Println("read i", i)
	rwmutex.RUnlock()
}

func write() {
	rwmutex.Lock()
	rand.Seed(time.Now().Unix())
	i = rand.Intn(10)
	fmt.Println("write i", i)
	rwmutex.Unlock()
}

func main() {
	go func() {
		write()
		read()
		time.Sleep(time.Second)
		write()
		read()
	}()
	time.Sleep(2 * time.Second)
}

执行结果: