Mutex锁

mutex源码分析

Locker接口:

type Locker interface {
   Lock()
   Unlock()
}

在 Go 的 sync 包中,Mutex 是一个用于互斥锁的结构体,其中 state 表示锁的状态,而 sema 是用于信号量的字段,它在需要阻塞或者唤醒等待的 goroutine 时发挥作用。Mutex 就实现了这个接口,Lock请求锁,Unlock释放锁

type Mutex struct {
   state int32   //锁状态,保护四部分含义
   sema  uint32  //信号量,用于阻塞等待或者唤醒
}

在这里插入图片描述

  • Locked:表示该 mutex 是否被锁定,0 表示没有,1 表示处于锁定状态;

  • Woken:表示是否有协程被唤醒,0 表示没有,1 表示有协程处于唤醒状态,并且在加锁过程中;

  • Starving:Go1.9 版本之后引入,表示 mutex 是否处于饥饿状态,0 表示没有,1 表示有协程处于饥饿状态;

  • Waiter: 等待锁的协程数量。

const (
    // mutex is locked ,在低位,值 1
   mutexLocked = 1 << iota

    //标识有协程被唤醒,处于 state 中的第二个 bit 位,值 2
   mutexWoken

    //标识 mutex 处于饥饿模式,处于 state 中的第三个 bit 位,值 4
   mutexStarving

    // 值 3,state 值通过右移三位可以得到 waiter 的数量
   // 同理,state += 1 << mutexWaiterShift,可以累加 waiter 的数量
   mutexWaiterShift = iota

    // 标识协程处于饥饿状态的最长阻塞时间,当前被设置为 1ms
   starvationThresholdNs = 1e6
)

Lock

func (m *Mutex) Lock() {
   // Fast path: grab unlocked mutex. //运气好,直接加锁成功
   if atomic.CompareAndSwapInt32(&m.state, 0, mutexLocked) {
      if race.Enabled {
         race.Acquire(unsafe.Pointer(m))
      }
      return
   }
   // Slow path (outlined so that the fast path can be inlined)
  //内联,加锁失败,就得去自旋竞争或者饥饿模式下竞争
   m.lockSlow()
}
func (m *Mutex) lockSlow() {
   var waitStartTime int64
   // 标识是否处于饥饿模式
   starving := false
   // 唤醒标记
   awoke := false
   // 自旋次数
   iter := 0
   old := m.state
   for {
      // 非饥饿模式下,开启自旋操作
      // 从 runtime_canSpin(iter) 的实现中(runtime/proc.sync_runtime_canSpin)可以知道,
      // 如果 iter 的值大于 4,将返回 false
      if old&(mutexLocked|mutexStarving) == mutexLocked && runtime_canSpin(iter) {
         // 如果没有其他 waiter 被唤醒,那么将当前协程置为唤醒状态,同时 CAS 更新 mutex 的 Woken 位
         if !awoke && old&mutexWoken == 0 && old>>mutexWaiterShift != 0 &&
            atomic.CompareAndSwapInt32(&m.state, old, old|mutexWoken) {
            awoke = true
         }
         // 开启自旋
         runtime_doSpin()
         iter++
         // 重新检查 state 的值
         old = m.state
         continue
      }
      new := old
      // 非饥饿状态
      if old&mutexStarving == 0 {
         // 当前协程可以直接加锁
         new |= mutexLocked
      }
      // mutex 已经被锁住或者处于饥饿模式
      // 那么当前协程不能获取到锁,将会进入等待状态
      if old&(mutexLocked|mutexStarving) != 0 {
         // waiter 数量加 1,当前协程处于等待状态
         new += 1 << mutexWaiterShift
      }
      // 当前协程处于饥饿状态并且 mutex 依然被锁住,那么设置 mutex 为饥饿模式
      if starving && old&mutexLocked != 0 {
         new |= mutexStarving
      }
      if awoke {
         if new&mutexWoken == 0 {
            throw("sync: inconsistent mutex state")
         }
         // 清除唤醒标记
         // &^ 与非操作,mutexWoken: 10 -> 01
         // 此操作之后,new 的 Locked 位值是 1,如果能够成功写入到 m.state 字段,那么当前协程获取锁成功
         new &^= mutexWoken
      }
      // CAS 设置新状态成功
      if atomic.CompareAndSwapInt32(&m.state, old, new) {
         // 旧的锁状态已经被释放并且处于非饥饿状态
         // 这个时候当前协程正常请求到了锁,就可以直接返回了
         if old&(mutexLocked|mutexStarving) == 0 {
            break
         }
         // 处理当前协程的饥饿状态
         // 如果之前已经处于等待状态了(已经在队列里面),那么将其加入到队列头部,从而可以被高优唤醒
         queueLifo := waitStartTime != 0
         if waitStartTime == 0 {
            // 阻塞开始时间
            waitStartTime = runtime_nanotime()
         }
         // P 操作,阻塞等待
         runtime_SemacquireMutex(&m.sema, queueLifo, 1)
         // 唤醒之后,如果当前协程等待超过 1ms,那么标识当前协程处于饥饿状态
         starving = starving || runtime_nanotime()-waitStartTime > starvationThresholdNs
         old = m.state
         // mutex 已经处于饥饿模式
         if old&mutexStarving != 0 {
            // 1. 如果当前协程被唤醒但是 mutex 还是处于锁住状态
            // 那么 mutex 处于非法状态
            //
            // 2. 或者如果此时 waiter 数量是 0,并且 mutex 未被锁住
            // 代表当前协程没有在 waiters 中,但是却想要获取到锁,那么 mutex 状态非法
            if old&(mutexLocked|mutexWoken) != 0 || old>>mutexWaiterShift == 0 {
               throw("sync: inconsistent mutex state")
            }
            // delta 代表加锁并且将 waiter 数量减 1 两步操作
            delta := int32(mutexLocked - 1<<mutexWaiterShift)
            // 非饥饿状态 或者 当前只剩下一个 waiter 了(就是当前协程本身)
            if !starving || old>>mutexWaiterShift == 1 {
               // 那么 mutex 退出饥饿模式
               delta -= mutexStarving
            }
            // 设置新的状态
            atomic.AddInt32(&m.state, delta)
            break
         }
         awoke = true
         iter = 0
      } else {
         old = m.state
      }
   }

   if race.Enabled {
      race.Acquire(unsafe.Pointer(m))
   }
}

解锁操作会根据 Mutex.state 的状态来判断需不需要去唤醒其他等待中的协程。

func (m *Mutex) unlockSlow(new int32) {
   // new - state 字段原子减 1 之后的值,如果之前是处于加锁状态,那么此时 new 的末位应该是 0
   // 此时 new+mutexLocked 正常情况下会将 new 末位变成 1
   // 那么如果和 mutexLocked 做与运算之后的结果是 0,代表 new 值非法,解锁了一个未加锁的 mutex
   if (new+mutexLocked)&mutexLocked == 0 {
      throw("sync: unlock of unlocked mutex")
   }
   // 如果不是处于饥饿状态
   if new&mutexStarving == 0 {
      old := new
      for {
         // old>>mutexWaiterShift == 0 代表没有等待加锁的协程了,自然不需要执行唤醒操作
         // old&mutexLocked != 0 代表已经有协程加锁成功,此时没有必要再唤醒一个协程(因为它不可能加锁成功)
         // old&mutexWoken != 0 代表已经有协程被唤醒并且在加锁过程中,此时不需要再执行唤醒操作了
         // old&mutexStarving != 0 代表已经进入了饥饿状态,
         // 以上四种情况,皆不需要执行唤醒操作
         if old>>mutexWaiterShift == 0 || old&(mutexLocked|mutexWoken|mutexStarving) != 0 {
            return
         }
         // 唤醒一个等待中的协程,将 state woken 位置为 1
         // old - 1<<mutexWaiterShift waiter 数量减 1
         new = (old - 1<<mutexWaiterShift) | mutexWoken
         if atomic.CompareAndSwapInt32(&m.state, old, new) {
            runtime_Semrelease(&m.sema, false, 1)
            return
         }
         old = m.state
      }
   } else {
      // 饥饿模式
      // 将 mutex 的拥有权转移给下一个 waiter,并且交出 CPU 时间片,从而能够让下一个 waiter 立刻开始执行
      runtime_Semrelease(&m.sema, true, 1)
   }
}

UnLock

// 解锁操作
func (m *Mutex) Unlock() {
   if race.Enabled {
      _ = m.state
      race.Release(unsafe.Pointer(m))
   }

   // mutexLocked 位设置为 0,解锁
   new := atomic.AddInt32(&m.state, -mutexLocked)
   // 如果此时 state 值不是 0,代表其他位不是 0(或者出现异常使用导致 mutexLocked 位也不是 0)
   // 此时需要进一步做一些其他操作,比如唤醒等待中的协程等
   if new != 0 {
      m.unlockSlow(new)
   }
}

信号量如何唤醒等待的goroutine

当一个 goroutine 尝试获取已经被锁定的 Mutex 时,如果锁不可用,goroutine 就会被阻塞。sema 字段在这个过程中起到了关键作用。具体过程如下:

  1. 获取锁失败:如果 Mutex 已经被其他 goroutine 锁定,那么尝试获取锁的 goroutine 会将自己加入等待队列,并被阻塞。

  2. 信号量作用:当其他 goroutine 释放这个锁时,Mutex 会通过 sema 来通知(唤醒)等待队列中的一个或多个被阻塞的 goroutine,表示锁现在已经可用。

  3. 唤醒等待的 goroutine:被唤醒的 goroutine 会从等待队列中取出,尝试重新获取锁。如果成功,则继续执行;如果仍然失败,则可能会继续阻塞或重试获取。

sync.Mutex 中,实际的信号量操作是通过 runtime_Semreleaseruntime_Semacquire 这两个底层函数来实现的:

  • runtime_Semacquire:用于阻塞当前 goroutine,等待信号量释放。当锁不可用时调用。
  • runtime_Semrelease:用于释放信号量,唤醒被阻塞的 goroutine。当锁被释放时调用。

这种机制确保了多个 goroutine 在访问共享资源时不会发生竞态条件,并能按照一定的顺序安全地访问共享资源。

等待队列在哪里保存

在 Go 的 sync.Mutex 实现中,等待队列并不显式地存在于 Mutex 结构体中,而是由 Go 运行时系统在底层管理的。

当一个 goroutine 尝试获取锁并且失败时,它会被阻塞并放入一个由 Go 运行时管理的内部等待队列中。这个等待队列的管理和调度是通过 Go 运行时的调度器(scheduler)来完成的,具体细节涉及到 Go 运行时的一些底层数据结构和机制,如 Goroutine 的状态管理、P(处理器)的管理等。

简而言之:

  1. 内部管理:等待队列由 Go 运行时管理,并且与具体的 Mutex 结构体的实现细节是解耦的。Mutex 结构体中不会直接保存这个队列。

  2. Goroutine 阻塞与唤醒:当 goroutine 需要等待时,Go 运行时会将其阻塞,并将其状态设置为等待信号量。这些 goroutine 会被组织到一个隐式的等待队列中,当锁释放时,Go 运行时通过信号量机制选择一个或多个 goroutine 并唤醒它们。

  3. runtime 包中的实现:Go 语言的 runtime 包负责这一过程,它使用了底层的系统调用和数据结构来实现 goroutine 的阻塞和唤醒。

因此,等待队列的管理是隐式的,不直接暴露在 Mutex 结构体中,而是由 Go 运行时系统在后台处理。

mutex两种运行模式

Go1.9 引入饥饿模式,尽可能让等待较长的goroutine有多更激活获取锁

饥饿模式是对公平性和性能的一种平衡,它避免了某些 goroutine 长时间的等待锁。在饥饿模式下,优先对待的是那些一直在等待的 waiter。

mutex normal 正常模式

默认情况下,Mutex的模式为normal。

该模式下,协程如果加锁不成功不会立即转入阻塞排队,而是判断是否满足自旋的条件,如果满足则会启动自旋过程,尝试抢锁。

正常模式 高吞吐量
在这里插入图片描述

自旋

自旋是一种多线程同步机制,当前的进程在进入自旋的过程中会一直保持 CPU 的占用,持续检查某个条件是否为真。
在多核的 CPU 上,自旋可以避免 Goroutine 的切换,使用恰当会对性能带来很大的增益,但是使用的不恰当就会拖慢整个程序,所以 Goroutine 进入自旋的条件非常苛刻:

mutex starvation 饥饿模式

自旋过程中能抢到锁,一定意味着同一时刻有协程释放了锁,我们知道释放锁时如果发现有阻塞等待的协程,还会释放一个信号量来唤醒一个等待协程,被唤醒的协程得到CPU后开始运行,此时发现锁已被抢占了,自己只好再次阻塞,不过阻塞前会判断自上次阻塞到本次阻塞经过了多长时间,如果超过1ms的话,会将Mutex标记为"饥饿"模式,然后再阻塞。

处于饥饿模式下,不会启动自旋过程,也即一旦有协程释放了锁,那么一定会唤醒协程,被唤醒的协程将会成功获取锁,同时也会把等待计数减1。

在饥饿模式下,Mutex 的拥有者将直接把锁交给队列最前面的 waiter。新来的 goroutine 不会尝试获取锁,即使看起来锁没有被持有,它也不会去抢,也不会 spin(自旋),它会乖乖地加入到等待队列的尾部。

如果拥有 Mutex 的 waiter 发现下面两种情况的其中之一,它就会把这个 Mutex 转换成正常模式:

  • 此 waiter 已经是队列中的最后一个 waiter 了,没有其它的等待锁的 goroutine 了;
  • 此 waiter 的等待时间小于 1 毫秒(ms)。

锁的底层实现类型

锁内存总线,针对内存的读写操作,在总线上控制,限制程序的内存访问

锁缓存行,同一个缓存行的内容读写操作,CPU内部的高速缓存保证一致性

锁,作用在一个对象或者变量上。现代CPU会优先在高速缓存查找,如果存在这个对象、变量的缓存行数据,会使用锁缓存行的方式。否则,才使用锁总线的方式。

RWMutex

RWMutex 源码

go1.22

type RWMutex struct {
    w           Mutex        // 写者互斥锁,保证写之间互斥
    writerSem   uint32       // 写者等待用信号量
    readerSem   uint32       // 读者等待用信号量
    readerCount atomic.Int32 // >=0 当前读者数量;<0 表示写者等待(写优先)
    readerWait  atomic.Int32 // 写者到来时,正在退出的读者数
}

通过记录 readerCount 读锁的数量来进行控制,当有一个写锁的时候,会将读 锁数量设置为负数 1<<30。目的是让新进入的读锁等待之前的写锁释放通知读 锁。同样的当有写锁进行抢占时,也会等待之前的读锁都释放完毕,才会开始 21 进行后续的操作。 而等写锁释放完之后,会将值重新加上 1<<30, 并通知刚才 新进入的读锁(rw.readerSem),两者互相限制。

RLock 获取读锁

// RLock 获取读锁。
// 多个读者可以并发持有读锁,但如果写锁正在等待,新来的读者必须阻塞。
func (rw *RWMutex) RLock() {

	// ---- Race Detector 处理(调试用,可忽略理解) ----
	// 如果开启 race 检测,访问写锁状态,并暂时关闭 race detector,
	// 避免读锁竞争导致的误报。
	if race.Enabled {
		_ = rw.w.state
		race.Disable()
	}

	// ---- 获取读锁的核心逻辑 ----
	// readerCount 是一个原子变量,表示正在持有读锁的数量。
	// Add(1) 表示新增一个读者。
	// 如果 Add(1) 后结果 < 0,表示当前有 writer 在等待,
	// Go 使用 readerCount < 0 作为 ⚠️"有写者等待" 的标志。
	if rw.readerCount.Add(1) < 0 {

		// --- 有写锁等待:读者必须阻塞 ---
		// 等待 writer 完成(或者释放 readerSem)。
		// readerSem 是读者等待队列的信号量。
		runtime_SemacquireRWMutexR(&rw.readerSem, false, 0)
	}

	// ---- Race Detector 处理(调试用) ----
	// 重新开启 race 检测,并标记当前 goroutine 成功获取读锁。
	if race.Enabled {
		race.Enable()
		race.Acquire(unsafe.Pointer(&rw.readerSem))
	}
}

RUnlock 释放读锁

// RUnlock 释放读锁。
// 如果释放后没有写者等待,则快速返回;
// 如果有写者等待(readerCount < 0),执行慢路径唤醒写者。
func (rw *RWMutex) RUnlock() {

    // ---- Race Detector 处理(调试相关)----
    if race.Enabled {
        _ = rw.w.state
        // 通知 race detector:当前 goroutine 不再持有对 writerSem 的访问权限
        race.ReleaseMerge(unsafe.Pointer(&rw.writerSem))
        race.Disable()
    }

    // ---- 核心逻辑:减少 readerCount ----
    // Add(-1) 表示减少一个读者。
    // r 是减少后的值。
    if r := rw.readerCount.Add(-1); r < 0 {
        // r < 0 表示写者正在等待(因为 writer 会把 readerCount 调整为极大负数)
        // 进入慢路径,处理写者的唤醒逻辑
        rw.rUnlockSlow(r)
    }

    // ---- Race Detector 恢复 ----
    if race.Enabled {
        race.Enable()
    }
}


// rUnlockSlow 是 RUnlock 的慢路径处理。
// 当释放读锁时,如果存在写锁正在等待,就会走这个路径。
// 主要作用:检查错误状态,并在最后一个读者退出时唤醒写者。
func (rw *RWMutex) rUnlockSlow(r int32) {

    // ---- 检查非法解锁 ----
    // r 是 RUnlock 时 readerCount 减 1 后的值
    // r+1 恢复到原始值
    // 如果 r+1 == 0 或 r+1 == -rwmutexMaxReaders
    // 说明:
    // 1. 没有读锁可释放(解锁次数过多)
    // 2. 读锁状态异常
    if r+1 == 0 || r+1 == -rwmutexMaxReaders {
        race.Enable()       // 重新启用 race 检测
        fatal("sync: RUnlock of unlocked RWMutex") // 报错:释放未持有的读锁
    }

    // ---- 写者正在等待 ----
    // 当 readerCount < 0 时,说明写锁正在等待
    // 每释放一个读锁,readerWait--,直到最后一个读者退出
    if rw.readerWait.Add(-1) == 0 {
        // 最后一个读者退出 → 唤醒写者
        runtime_Semrelease(&rw.writerSem, false, 1)
    }
}


Lock 获取写锁

// Lock 获取写锁。
// 如果锁已被读或写占用,则阻塞等待。
func (rw *RWMutex) Lock() {

    // ---- Race Detector 处理 ----
    if race.Enabled {
        _ = rw.w.state
        race.Disable()
    }

    // ---- 处理写者竞争 ----
    // rw.w 是内部互斥锁(Mutex),保证多个写者不会同时进入慢路径
    rw.w.Lock()

    // ---- 通知读者有写者等待 ----
    // rwmutexMaxReaders 是一个很大的常数(1 << 30)
    // readerCount.Add(-rwmutexMaxReaders) 将 readerCount 减到负数
    // 后来的读者会看到 < 0,阻塞自己
    r := rw.readerCount.Add(-rwmutexMaxReaders) + rwmutexMaxReaders

    // ---- 等待当前活跃读者完成 ----
    // r 是原先活跃读者数量
    if r != 0 && rw.readerWait.Add(r) != 0 {
        // 当前有读者正在持锁,阻塞写者, 写者正在等待所有活跃读者释放读锁, 它会被挂起,不占用 CPU
        runtime_SemacquireRWMutex(&rw.writerSem, false, 0)
    }

    // ---- Race Detector 处理 ----
    if race.Enabled {
        race.Enable()
        race.Acquire(unsafe.Pointer(&rw.readerSem))
        race.Acquire(unsafe.Pointer(&rw.writerSem))
    }
}

简化理解
写者执行 Lock():
    readerCount -= MAX
    if 当前有读者:
        -> 阻塞自己(runtime_SemacquireRWMutex)
    else:
        -> 直接获得写锁

Unlock 释放写锁

// Unlock 释放写锁。
// 如果当前未持有写锁,运行时报错。
func (rw *RWMutex) Unlock() {

    // ---- Race Detector 处理 ----
    if race.Enabled {
        _ = rw.w.state
        race.Release(unsafe.Pointer(&rw.readerSem))
        race.Disable()
    }

    // ---- 通知读者写锁已释放 ----
    // readerCount += rwmutexMaxReaders,将 readerCount 恢复到正常状态
    r := rw.readerCount.Add(rwmutexMaxReaders)
    if r >= rwmutexMaxReaders {
        race.Enable()
        fatal("sync: Unlock of unlocked RWMutex") // 非法解锁
    }

    // ---- 唤醒阻塞的读者 ----
    // r 是写锁释放前阻塞的读者数量
    for i := 0; i < int(r); i++ {
        runtime_Semrelease(&rw.readerSem, false, 0)
    }

    // ---- 允许其他写者继续 ----
    rw.w.Unlock()

    // ---- Race Detector 恢复 ----
    if race.Enabled {
        race.Enable()
    }
}


读写锁实现总结

RWMutex 的核心变量和机制:

流程概览:

  1. 读锁 RLock

    • 增加 readerCount
    • 如果有写者等待 (readerCount < 0) → 阻塞在 readerSem
    • 释放读锁时 RUnlock → 减少 readerCount → 如果最后一个读者 → 唤醒写者
  2. 写锁 Lock

    • 获取内部 Mutex w → 排队写者
    • readerCount 减到负数 → 阻塞新读者
    • 等待活跃读者完成 (runtime_SemacquireRWMutex)
    • 写锁持有期间独占访问
  3. 写锁 Unlock

    • 恢复 readerCount
    • 唤醒阻塞的读者 (runtime_Semrelease)
    • 释放内部 Mutex w → 下一个写者可以竞争

读写锁的基本使用

在 Go 中,读写锁(sync.RWMutex)允许多个 goroutine 同时读取共享资源,但在写入时,必须独占锁,即只有一个 goroutine 能够进行写操作,且在写操作期间不允许其他 goroutine 进行读操作。

读写锁的基本使用包括:

  • 读锁:使用 RLock() 方法加读锁,RUnlock() 方法释放读锁。
  • 写锁:使用 Lock() 方法加写锁,Unlock() 方法释放写锁。
var rwMutex sync.RWMutex
var sharedData int

func readData() int {
    rwMutex.RLock()         // 加读锁
    defer rwMutex.RUnlock() // 在函数返回时释放读锁
    return sharedData       // 读取共享数据
}

func writeData(data int) {
    rwMutex.Lock()          // 加写锁
    defer rwMutex.Unlock()  // 在函数返回时释放写锁
    sharedData = data       // 写入共享数据
}

读写锁代码示例

package main

import (
	"fmt"
	"sync"
	"time"
)

var (
	rwMutex    sync.RWMutex
	sharedData int
	wg         sync.WaitGroup
)

func readData(id int) {
	defer wg.Done()
	rwMutex.RLock()
	fmt.Printf("Goroutine %d: Reading data: %d\n", id, sharedData)
	time.Sleep(time.Second)
	rwMutex.RUnlock()
}

func writeData(id, data int) {
	defer wg.Done()
	rwMutex.Lock()
	fmt.Printf("Goroutine %d: Writing data: %d\n", id, data)
	sharedData = data
	time.Sleep(time.Second)
	rwMutex.Unlock()
}

func main() {
	wg.Add(3)

	go readData(1)
	go readData(2)
	go writeData(3, 42)

	wg.Wait()
	fmt.Println("Final data:", sharedData)
}

在这个例子中,两个 goroutine 同时读取共享数据,而另一个 goroutine 写入数据。写操作会等待当前的读操作完成,然后才能进行写入。

其他共享内存线程安全的方式

官方不太推荐使用锁,更多的是通过channel做数据交换

思考

如何设计一个并发更高的锁?

在Go语言中,使用切片来设计并发更高效的锁是一种常见的做法,通常被称为"分段锁"或"分片锁"。

这种技术可以在一定程度上减小锁的粒度,从而提高并发性能。

package main

import (
	"fmt"
	"sync"
	"hash/fnv"
)

const numSegments = 16

type ConcurrentMap struct {
	segments []sync.Mutex
	data     map[interface{}]interface{}
}

func NewConcurrentMap() *ConcurrentMap {
	segments := make([]sync.Mutex, numSegments)
	data := make(map[interface{}]interface{})
	return &ConcurrentMap{segments: segments, data: data}
}

func (cm *ConcurrentMap) getSegment(key interface{}) *sync.Mutex {
	hash := hashFunction(key) % numSegments
	return &cm.segments[hash]
}

func (cm *ConcurrentMap) Get(key interface{}) interface{} {
	segment := cm.getSegment(key)
	segment.Lock()
	defer segment.Unlock()

	return cm.data[key]
}

func (cm *ConcurrentMap) Set(key, value interface{}) {
	segment := cm.getSegment(key)
	segment.Lock()
	defer segment.Unlock()

	cm.data[key] = value
}

// 假设的哈希函数,仅用于示例目的
func hashFunction(key interface{}) int {
	h := fnv.New32a()
	// 将键的字节表示写入哈希函数
	_, _ = h.Write([]byte(fmt.Sprintf("%v", key)))
	return int(h.Sum32())
}

func main() {
	concurrentMap := NewConcurrentMap()

	var wg sync.WaitGroup
	numItems := 1000

	for i := 0; i < numItems; i++ {
		wg.Add(1)
		go func(index int) {
			defer wg.Done()
			key := fmt.Sprintf("key%d", index)
			concurrentMap.Set(key, index)
		}(i)
	}

	wg.Wait()

	// 输出结果
	for i := 0; i < numItems; i++ {
		key := fmt.Sprintf("key%d", i)
		fmt.Printf("%s: %v\n", key, concurrentMap.Get(key))
	}
}

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐