Mutex和channel 都可以在并发环境下对资源进行保护,避免竞态, channel 在 golang 中一直被追捧,但是既然都能解决问题,但为什么还要弄两个东西呢?

查阅一些文章,发现有些时候对于channel过于追捧了。有时候该用Mutex 还是要用Mutex的,不要为了用channel 而用channle, 需要区分不同的场景

我们以一个例子,在多个协程下,对公共变量执行减一操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
	"fmt"
	"sync"
)

var total int = 1000
var wg sync.WaitGroup

func main() {
	for i:=0; i<1000; i++{
		wg.Add(1)
		go func() {
			defer wg.Done()
			total -= 1
		}()
	}
	wg.Wait()
	// 打印一下total的值
	fmt.Println(total)
}

有一个公共变量total 为1000, 之后启1000个协程,每个协程对total 进行减一操作,我们想得到0的结果,但是由于多个协程同时操作一个变量,在没有加锁的情况,最后得到的结果很大情况下不为0的,且每次的结果也都不太一样。

使用锁Mutex

由于存在对同一个资源进行操作的情况,所以需要加上锁

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package main

import (
	"fmt"
	"sync"
)

var total int = 1000
var wg sync.WaitGroup
var lock = sync.Mutex{}

func main() {
	for i:=0; i<1000; i++{
		wg.Add(1)
		go func() {
			lock.Lock()
			defer wg.Done()
			defer lock.Unlock()
			total -= 1
		}()
	}
	wg.Wait()
	// 打印一下total的值
	fmt.Println(total)
}

在匿名函数go func中,先进行lock.Lock() 加锁,之后在延迟处理解锁,与wg的Done操作。

这次由于加了锁,最后输出的结果为0 了

使用通道channel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
func main() {
	var done = make(chan bool, 1)
	done <- true
	for i:=0; i<1000; i++{
		wg.Add(1)
		go func() {
			<- done  //多协程下如果取不到会阻塞
			defer wg.Done()
			total -= 1
			done <- true
		}()
	}
	wg.Wait()
    // 打印一下total的值
	fmt.Println(total)
}

在匿名函数中使用channel 对共享数据进行保护

Mutex和channel 的选择

  1. Channel 是 Go 中的高级概念,Go 中某些程序仅使用 Mutex, Go 的 Channel 很吸引人因为它们提供了内置的线程安全性,并鼓励对共享的关键资源进行单线程访问。 但是与 Mutex 相比,Channel 会导致性能下降。 当只需要锁定少量共享资源时,使用 Mutex 非常有用。 如果 Mutex 很适合你的需求请放心使用 sync.Mutex。
  2. 如果需要将数据资源在各个协程间进行流动那么需要使用channel
  3. channel 关注数据流动,如果任务处理模型中存在数据流动,使用channel 解决;
  4. 数据的控制权需要在多个gorutine 中传递, 使用 channel 处理;

参考

在 Go 中使用 Mutex 与 Channel 进行状态同步

Golang并发:再也不愁选channel还是选锁

golang channel 使用总结