Goroutine与Channel: 锁之外的另一种同步机制

在主流的编程语言中为了保证多线程之间共享数据安全性和一致性,都会提供一套基本的同步工具集,如锁,条件变量,原子操作等等。Go语言标准库也毫不意外的提供了这些同步机制,使用方式也和其他语言也差不多。

除了这些基本的同步手段,Go语言还提供了一种新的同步机制: Channel,它在Go语言中是一个像int, float32等的基本类型,一个channel可以认为是一个能够在多个Goroutine之间传递某一类型的数据的管道。Go中的channel无论是实现机制还是使用场景都和Java中的BlockingQueue很接近。

  1. // 声明channel变量
  2. var syncChan = make(chan int) // 无缓冲channel,主要用于两个Goroutine之间建立同步点
  3. var cacheChan = make(chan int, 10) // 缓冲channel
  4. // 向channel中写入数据
  5. syncChan <- 1
  6. cacheChan <- 1
  7. // 从channel读取数据
  8. var i = <-syncChan
  9. var j = <-cacheChan

几乎等价于的Java中的操作:

  1. TransferQueue<Integer> syncQueue = new LinkedTransferQueue<Integer>();
  2. BlockingQueue<Integer> cacheQueue = new ArrayBlockingQueue<Integer>(10);
  3. syncQueue.transfer(1);
  4. cacheQueue.put(1);
  5. int i = syncQueue.take();
  6. int j = cacheQueu.take();

a. 与Java的BlockingQueue一样用在需要生产者消费者模型的并发环境中。

b. 锁同步场景下一种替代方案。在Go的并发编程中有一句很经典的话:不要以共享内存的方式去通信,而要以通信的方式去共享内存。在Go语言中并不鼓励用锁保护共享状态的方式在不同的Goroutine中分享信息(以共享内存的方式去通信)。而是鼓励通过channel将共享状态或共享状态的变化在各个Goroutine之间传递(以通信的方式去共享内存),这样同样能像用锁一样保证在同一的时间只有一个Goroutine访问共享状态。但这的确需要转换以前用锁做并发同步的思维方式,大家觉得那种适合自己和自己的使用场景就用哪种好了,并不能很简单、绝对地说哪种方式更好,更高效。