1.使用通道
如果说Goroutine是一种支持并发编程的方式,那么通道就是一种与Goroutine通信的方式。通道让数据能够进入和离开Goroutine,可方便Goroutine之间进行通信。
“不要通过共享内存来通信,而通过通信来共享内存。”
这句话说明了Go语言并发实现方式的不同之处,这部分有必要做进一步解释。在其他编程语言中,并发编程通常是通过在多个进程或线程之间共享内存实现的。共享内存能够让程序同步,确保程序以合乎逻辑的方式执行。在程序执行过程中,进程或线程可能对共享内存加锁,以禁止其他进程或线程修改它。这合乎情理,因为如果在操作期间共享内存被其他进程修改,可能会带来灾难性后果--引发Bug或导致程序崩溃。通过这种方式给内存加锁,可确保它是独占的--只有一个进程或线程能够访问它。
这听起来可能太过抽象,我们来看一个例子:两个人持有一个联名帐户,他们同时要从这个帐户支付费用,但这两笔交易的总额超过了帐户余额。如果两个交易同时进行且不加锁,则余额检查可能表明资金充足,但实际上资金不够;然而,如果第一个交易将帐户加锁,则直到交易完成,都可避免这样的情况发生。对于简单的并发而言,这种方式看似合理,但如果联名帐户有20个持有人,且他们经常使用这个帐户进行交易呢?在这种情况下,加锁管理工作或能非常复杂。
共享内存和锁的管理工作并非那么容易,很多编程语言要求程序员对内存和内存管理有深入认识。即便是久经沙场的程序员也会遇到这样的情形,即为找出进程或线程争用共享内存而引发的Bug,需要花费数天时间。在使用共享内存的并发环境中,如果不能始终知道程序的哪部分将先更新数据,那么将难以推断其中发生的情况。
虽然使用共享内存有其用武之地,但Go语言使用通道在Goroutine之间收发消息,避免了使用共享内存。严格地说,Goroutine并不是线程,但您可将其视为线程,因为它们能够以非阻塞方式执行代码。在前面关于两人持有一个联合帐户的例子中,如果使用Goroutine,将可能向通道发送一条消息,而通道可能限制后续交易或另一个帐户持有人的行为。通过收发消息,使得能够以推送方式协调并发事件。事件发生时,可将触发的消息推送给接收者。使作共享内存时,程序必须检查共享内存。在变化频繁的并发编程环境中,很多人都认为使用消息是一种更佳的通信方式。
下列程序使用了Goroutine来执行运行速度缓慢的函数,以免阻塞整个程序的执行。
package main
import (
"fmt"
"time"
)
func slowFunc() {
time.Sleep(time.Second * 2)
fmt.Println("sleeper() finished")
}
func main() {
go slowFunc()
fmt.Println("I am not shown until slowFunc( ) completes")
time.Sleep(time.Second * 3)
}
在程序中,使用了一个定时器,旨在避免程序在 Goroutine 返回前退出。在介绍Goroutine的示例中,这样做完全可行,但在更复杂的并发程序中,使用定时器绝非好主意。为了管理 Goroutine 和并发,GO 语言提供了通道。在上面的程序中,如果能够在 Goroutine 和程序之间通信,并让 Goroutine 结束时能够告诉主程序就好了.这正是通道的用武之地.
通道的创建语法如下:
c := make(chan string)
语法解读如下:
- 使用简短变量赋值,将变量 c 初始化为 := 右边的值
- 使用内置函数 make 创建一个通道,这里使用关键字 chan 指定的
- 关键字 chan 后面的 string 指出这个通道将用于存储字符串数据,这意味着这个通道只能用于收发字符串值
向通道发送消息的语法如下:
c <- "Hello World"
请注意其中的 <- ,这表示将右边的字符串发送给左边的通道.如果通道被指定为收发字符串,则只能向它发送字符串消息,如果向它发送其他类型的消息将导致错误.
<