channel
channel
单纯的将函数执行并发是没有意义的,函数与函数间需要交换数据才能体现并发执行函数的意义。虽然可以使用内存进行数据交换,但是共享内存在不同的goroutine中容易发生竞态问题。
为了保证数据交换的正确性,必须使用互斥锁对内存进行加锁,这种做法势必造成性能问题。
Go语言的并发模型是CSP,提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说goroutine是执行Go程序并发的执行体,channel就是它们之间的链接。channel是一个可以让goroutine发送特定值到另一个goroutine的通信机制。
Go中的channel是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,就是说声明channel的时候需要为其指定元素类型。
channel类型
声明channel类型的格式如下
var 变量 chan 元素类型
示例:
var ch1 chan int // 声明一个传递整型的通道 var ch2 chan string // 声明一个传递字符串类型的通道
创建channel
通道是引用类型,通道的空值是nil
var ch chan int fmt.Println(ch) // <nil>
声明的通道需要使用make函数初始化后才能使用
make(chan 元素类型,[缓冲大小])
缓冲大小是可选的,如果没有缓冲区大小,则必须有接收者接收,否则会卡住。
var ch1 chan string // 声明 ch1 = make(chan string, 10) // 初始化,必须使用make初始化,否则不能用 for i := 1; i < 10; i++ { str := "lifq" + strconv.Itoa(i) ch1 <- str } v1 := <-ch1 v2 := <-ch1 v3 := <-ch1 fmt.Println(v1, v2, v3) // lifq1 lifq2 lifq3
channel 操作
发送
将一个值发送到通道中
ch <- 10
接收
从一个通道中接收一个值
x := <- ch <- ch 从ch中接收值,忽略结果
关闭
通过调用内置的close函数来关闭通道
close(ch)
关闭通道需要注意的是,只有在通知接收方goroutine所有的数据全部发送完毕的时候才需要关闭通道。通道是可以被垃圾回收机制回收的,它和关闭文件不一样,在操作结束后关闭文件是必须的,但是关闭通道不是必需的。
channel案例
需求:
1. 启动一个goroutine,生成100个数发送到ch1
2. 启动一个goroutin,从ch1中取值,计算它的平方放到ch2
package main import ( "fmt" "sync" ) var wg sync.WaitGroup func f1(ch1 chan int) { defer wg.Done() for i := 0; i < 60; i++ { ch1 <- i } close(ch1) } func f2(ch1 chan int, ch2 chan int) { defer wg.Done() for { x, ok := <-ch1 if !ok { break } ch2 <- x * x } close(ch2) } func main() { a := make(chan int, 100) b := make(chan int, 100) wg.Add(2) go f1(a) go f2(a, b) wg.Wait() for { i, ok := <-b if !ok { break } fmt.Println(i) } }