单机并发实践 | 青训营

67 阅读2分钟

当多个协程(goroutine)同时读写同一个变量,在并发度较高的情况下,会发生冲突。确保一次只有一个协程(goroutine)可以访问该变量以避免冲突,这称之为互斥,互斥锁可以解决这个问题。

sync.Mutex 是 Go 语言标准库提供的一个互斥锁,当一个协程(goroutine)获得了这个锁的拥有权后,其它请求锁的协程(goroutine) 就会阻塞在 Lock() 方法的调用上,直到调用 Unlock() 锁被释放。

var m sync.Mutex
var set = make(map[int]bool, 0)

func printOnce(num int) {
	m.Lock()
	if _, exist := set[num]; !exist {
		fmt.Println(num)
	}
	set[num] = true
	m.Unlock()
}

func main() {
	for i := 0; i < 10; i++ {
		go printOnce(100)
	}
	time.Sleep(time.Second)
}

在上述这个代码例子中,我们用互斥锁的Lock()Unlock() 方法将代码内存冲突的部分包裹起来,这样相同的数字只会被打印一次。当一个协程调用了 Lock() 方法时,其他协程被阻塞了,直到Unlock()调用将锁释放。因此被包裹部分的代码就能够避免冲突,实现互斥。这样其它请求锁的协程(goroutine) 就会阻塞在 Lock() 方法的调用上,直到调用 Unlock() 锁被释放。

此外,在操作系统中存在多种锁,互斥锁(Mutex)和自旋锁(Spinlock)是两种用于同步和保护共享资源的锁机制,它们都可以防止多个线程或进程同时访问共享资源,从而避免竞态条件(Race Condition)和数据不一致等问题。

互斥锁(Mutex) : 互斥锁是一种用于实现线程或进程间同步的机制。当一个线程获得互斥锁并访问共享资源时,其他试图获得该锁的线程将被阻塞,直到锁被释放。互斥锁可以保证同一时刻只有一个线程能够访问共享资源。互斥锁的底层实现通常依赖于操作系统的原语,例如在Linux系统中使用pthread库的pthread_mutex_t数据结构来实现互斥锁。

自旋锁(Spinlock) : 自旋锁是一种低级的同步原语,通常用于多处理器或多核系统中。与互斥锁不同,当一个线程尝试获得自旋锁时,如果锁已经被其他线程持有,它将不断循环(“自旋”)检查锁是否可用,而不是进入阻塞状态。自旋锁适用于锁持有时间较短且线程不希望在等待锁时进入睡眠状态的场景。自旋锁的底层实现通常依赖于原子操作和CPU指令,如测试和设置(test-and-set)或比较和交换(compare-and-swap)等。