go 语言并发之互斥锁

互斥锁介绍

在并发编程过程中, 如果我们使用协程对同一个变量进行操作( 如加减 ), 会出现并发问题( 因为并发过程中变量的操作并不可控 ), 一个例子 :

对一个数字加减各100 次, 普通逻辑 :

package main

var num int = 100

func add() {
	num++
}
func sub() {
	num--
}

func main() {
	for i := 0; i < 100; i++ {
		add()
	}
	for i := 0; i < 100; i++ {
		sub()
	}
	println(num)
}

上面的代码顺序执行, 结果为 100, 下来我们使用协程来执行同样的过程 :

package main

import "sync"

var num int = 100
var wg sync.WaitGroup

func add() {
	num++
	wg.Done()
}
func sub() {
	num--
	wg.Done()
}

func main() {
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go add()
	}
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	println(num)
}

在上面的代码中使用 go 关键字开启了 200 个协程, 通过结果观察,我们发现变量结果是错误的,因为并发本身就是一种随机运行.

互斥锁

我们可以通过加锁的方式来保证在协程的运行过程中变量的操作过程是可控的, 语法 :

var lock sync.Mutex
lock.lock 加锁
lock.unlock 解锁

示例 :

package main

import "sync"

var num int = 100
var wg sync.WaitGroup
var lock sync.Mutex

func add() {
	lock.Lock()
	num++
	wg.Done()
	lock.Unlock()
}
func sub() {
	lock.Lock()
	num--
	wg.Done()
	lock.Unlock()
}

func main() {
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go add()
	}
	for i := 0; i < 100; i++ {
		wg.Add(1)
		go sub()
	}
	wg.Wait()
	println(num)
}

一些总结

锁可以解决协程并发的问题, 但是因为锁让原本高效的多线程变得低效了, 因为在锁定的过程中其他线程并没有执行, 建议您学习后面关于管道的知识,利用管道来处理变量操作.