一、内部结构

sync.Map主要由两个数据结构组成:

  1. 一个原生的 map:用于存储实际的键值对。这个 map在读取时无需加锁,但在写入时需要加锁。
  2. 一个 read结构体:包含一个只读的原生 map(称为 dirty map)和一个 misses计数器。这个 read结构体在读取操作时被优先使用,因为读取它无需加锁,只有在发生读未命中且 misses计数达到一定阈值时,才会将 dirty map中的内容写入到主 map中并重置 read结构体。

二、读写操作

1. 读操作:

  • 首先尝试从 read结构体中的 dirty map读取键对应的值。如果找到了值,直接返回。
  • 如果在 dirty map中未找到值,再尝试从主 map中读取。如果找到了值,更新 read结构体中的 dirty map并返回值。
  • 如果在主 map中也未找到值,增加 misses计数器。如果 misses计数器达到一定阈值,会将 dirty map中的内容写入主 map,并重置 read结构体和 misses计数器。

2. 写操作:

  • 首先锁定主 map,然后进行写入操作。
  • 同时,如果 read结构体中的 dirty map为空,将主 map复制一份到 dirty map中,以便后续的读操作可以更快地访问。

三、并发安全

1.读写分离设计

内部包含两个主要的数据结构:

  • 一个是普通的 map,用于存储实际的键值对,称为 “dirty map”。这个 map在写操作时会被锁定,以确保写操作的原子性和一致性。
  • 另一个是只读的 map结构,在执行读操作时优先从这个结构中读取数据,无需加锁,从而允许多个线程并发读取。

2.写操作的同步机制

互斥锁保护:

  • 当进行写操作(存储新的键值对、更新现有键值对或删除键值对)时,会获取一个互斥锁,确保同一时刻只有一个线程可以对 “dirty map” 进行写操作。
  • 这样可以防止多个线程同时修改 map时可能导致的数据不一致和竞争条件。

3.读操作的优化与同步

  1. 优先从只读map读取:

    • 读操作首先尝试从只读的 map中获取键对应的值,因为这个 map不需要加锁,所以多个线程可以同时进行读操作,提高了读操作的并发性能。
  2. 动态更新只读map:

    • 如果读操作在只读 map中未找到键对应的值,会尝试从 “dirty map” 中读取。如果在 “dirty map” 中找到了值,会将这个值更新到只读 map中,以便后续的读操作可以更快地获取到这个值。
  3. misses计数器:

    • 每次读操作在只读 map和 “dirty map” 中都未找到值时,会增加一个 misses计数器。当 misses计数器达到一定阈值时,会触发将 “dirty map” 中的内容复制到只读 map中,并重置 misses计数器。这个机制确保了在一段时间内如果读操作频繁未命中,会动态地更新只读 map,以提高后续读操作的效率。

4.删除操作的安全处理

标记删除:

  • 当执行删除操作时,会在 “dirty map” 中标记键值对为已删除状态。这样在后续的读操作中,如果在只读 map中未找到键对应的值,并且在 “dirty map” 中发现该键被标记为已删除,就可以确定该键不存在于 map中。