锁作用

在 Go 语言中使用 sync.Mutex 进行加锁操作时,​​锁的作用范围是基于锁实例本身,而不是针对特定函数​​。

1 核心概念:锁保护的是数据,而不是代码

2 锁作用于互斥量实例

var mu sync.Mutex // 这是一个锁实例  
func A() {     
    mu.Lock()     
    defer mu.Unlock()     
    // 临界区代码 
    }  
func B() {     
    mu.Lock()     
    defer mu.Unlock()     
    // 另一个临界区代码 
}

在这个例子中:

  • A() 持有了 mu 的锁时

  • 所有试图在 B()A() 或其他函数中锁定同一个 mu 的 goroutine 都会被阻塞

  • 无论这些函数在哪里被调用

锁保护的是代码执行路径,而不是变量本身​​。

3 锁使用的基本原则

  1. ​一对一原则​​:每个共享资源应该有自己专用的锁

  1. ​最小化原则​​:锁住的范围应尽量小

  1. ​层级原则​​:如果多个资源有关联,用更高层锁保护

锁使用情况

在并发编程中,当多个goroutine(协程)需要访问和修改共享资源时,为了防止数据竞争(data race)和保证数据一致性,我们需要使用锁来同步对共享资源的访问。以下情况需要使用锁:

  1. ​修改共享变量​​:当多个goroutine会同时修改(写入)同一个变量时。

  2. ​读写共享变量​​:当至少有一个goroutine修改共享变量,同时其他goroutine会读取该变量(读操作和写操作同时存在)。

  3. ​操作非原子性的复合结构​​:即使操作看起来是读(如读取一个结构体的多个字段),如果这个读操作期间不允许其他goroutine修改,那么也需要锁(或者使用读写锁中的读锁)。

但是,以下情况不需要锁:

  1. ​只读操作​​:当所有goroutine都只读取共享变量且没有任何写入操作时,因为只读不会造成数据不一致。

  2. ​原子操作​​:某些简单的操作(如整数的自增)可以使用原子操作(sync/atomic包)来避免锁的使用。但是原子操作仅限于简单的整数类型,对于复杂的数据结构还是需要锁。

  3. ​线程局部存储​​:如果每个goroutine操作的是自己的数据(即不共享),则不需要锁。

最后更新于