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