引入
试想这一个情况,两个同时运行的线程,它们都需要改写共享内存中的某一个数据,那会发生什么情况呢?
我们当然希望它们来安装顺序来,一个线程先读取,然后改写,再到另一个线程读取改写。
但很多情况下,并不是这样了,因为这两个线程它们之间是并不知道对方也同时在访问这个共享内存。
于是,可能出现下面这种我们并不想让它发生的情况:
两个线程(不妨记作A,B)同时读取了共享内存,获得了相同的数据。
其中A线程运算完后,改写了共享内存,然而此时B线程并不知道共享内存已经被改写,仍然用着改写之前的数据进行运算。
B线程得到了用改写之前数据得出的运算结果,并改写共享内存。
不难发现,虽然A,B线程都执行完了,但对于这个数据却只有B线程的操作,而A线程的操作被覆盖了。
互斥锁就为解决这一类问题提供了一个方案。
互斥锁
mux.Lock()
mux.Unlock()
通过Lock来加锁,如果此时mux是被锁上的,那么Lock函数就会阻塞,直到mux被Unlock为止。
死锁
如果一个锁一直被锁上,造成了某些线程阻塞,就称之为死锁。
造成死锁的常见情况:
- 连续调用两次Lock函数
- 在终结某些线程之前,没有正确地使用Unlock函数
在程序中,一定要避免死锁的情况出现。
Mutex
mux := &sync.Mutex{}
Mutex在同一个时间内只有一个线程能获得锁,
但假设现在有多个线程需要读取共享内存的某个数据,且这些线程并不会修改共享内存,那这样用Mutex 一个一个按顺序来读取不免有些效率低下。
RWMutex
mux := &sync.RWMutex{}
mux.Lock()
mux.Unlock()
mux.RLock()
mux.RUnlock()
RWMutex就很好地解决了并行读取的情况。
最后通过经典的生产者消费者模型来更好地理解互斥锁。