“不要通过共享内存来通信,而应该通过通信来共享内存。”这句话来自Go语言社区,这是为什么,今天我们来对比一下。
var counter int = 0
func Count(lock *sync.Mutex) {
lock.Lock()
counter++
fmt.Println(counter)
lock.Unlock()
}
func CountT() {
lock := &sync.Mutex{}
for i := 0; i < 10; i++ {
go Count(lock)
}
for {
lock.Lock()
c := counter
lock.Unlock()
runtime.Gosched()
if c >= 10 {
break
}
}
}
在上面的例子中,我们在10个goroutine中共享了变量counter。每个goroutine执行完成后,将counter的值加1。因为10个goroutine是并发执行的,所以我们还引入了锁,也就是代码中的lock变量。每次对counter的操作,都要先将锁锁住,操作完成后,再将锁打开。在主函数中,使用for循环来不断检查counter的值(同样需要加锁)。当其值达到10时,说明所有goroutine都执行完毕了,这时主函数返回,程序退出。
同样的例子用共享消息来实现
var counter int = 0
func Count(ch chan int) {
counter++
fmt.Println(counter)
ch <- 1
}
func CountT() {
chs := make([]chan int, 10)
for i := 0; i < 10; i++ {
chs[i] = make(chan int)
go Count(chs[i])
}
for _, ch := range chs {
<-ch
}
}
在这个例子中,我们定义了一个包含10个channel的数组(名为chs),并把数组中的每个channel分配给10个不同的goroutine。在每个goroutine的Add()函数完成后,我们通过ch <- 1语句向对应的channel中写入一个数据。在这个channel被读取前,这个操作是阻塞的。在所有的goroutine启动完成后,我们通过<-ch语句从10个channel中依次读取数据。在对应的channel写入数据前,这个操作也是阻塞的。这样,我们就用channel实现了类似锁的功能,进而保证了所有goroutine完成后主函数才返回。是不是比共享内存的方式更简单、优雅呢?