概念
- 竞态条件:数据被多个线程共享,很可能会产生争用和冲突的情况,往往会破坏共享数据的一致性(一致性即多个线程对共享数据的操作可以到达各自的预期),这种情况称为竞态条件。
- 为了解决这种情况,需要
同步
,用途有两个:避免多个线程同一时刻操作同一个数据和避免多个线程同一时刻执行同一段代码。 - 这样的数据块和代码块隐含着一种或多种资源(存储资源、计算资源、I/O资源、网络资源等),可以看作是共享资源,
同步就是控制多个线程对共享资源的访问
。 - 一个线程想要访问某一个共享资源,必须先申请访问权限(加锁),申请成功后才能访问,访问结束后,必须归还权限(解锁)。
- 多个并发运行的线程对共享资源的访问时完全串行的,只要一个代码片段需要实现对共享资源的穿行化访问,就可以被视为一个临界区,即要访问到资源必须进入的区域。
临界区需要被保护,否则会产生竞态条件,施加保护的重要手段之一,就是使用同步工具
互斥锁(sync.Mutex)
- 不要重复锁定互斥锁。不要忘记解锁。
- 不要对未锁定或者已解锁的互斥锁解锁,这样会产生fatal error,无法recover,程序会崩溃!
- 不要再多个函数之间直接传递互斥锁,因为互斥锁是结构体,是值类型的,传递的是副本,副本的所有操作,对原锁没有影响。
- 强烈建议在mu.Lock()之后,使用defer mu.Unlock()
- 多个goroutine对锁的争抢要看哪个goroutine等待时间最长(队列)
读写锁(sync.RWMutex)
- sync.RWMutex类型中的Lock方法和Unlock方法分别用于对写锁进行锁定和解锁,而它的RLock方法和RUnlock方法则分别用于对读锁进行锁定和解锁。
- 读写阻塞、写写阻塞、但是读读并不会造成阻塞。
- 对写锁解锁,会唤醒所有因试图锁定读锁而阻塞的goroutine,并且通常会使它们成功完成对读锁的锁定。
- 对读锁进行解锁,只会在没有其他读锁锁定的前提下,唤醒因试图锁定写锁而被锁定的goroutine(等待最长的那个)。
- 尝试解锁未被锁定的读锁或写锁都会fatal error!