channel类型
单纯地将函数并发执行是没有意义的。函数与函数间需要交换数据才能体现并发执行函数的意义。
虽然可以使用共享内存进行数据交换,但是共享内存在不同的 goroutine 中容易发生竞态问题。为了保证数据交换的正确性,很多并发模型中必须使用互斥量对内存进行加锁,这种做法势必造成性能问题。
Go语言采用的并发模型是CSP(Communicating Sequential Processes),提倡通过通信共享内存而不是通过共享内存而实现通信。
如果说 goroutine 是Go程序并发的执行体,channel就是它们之间的连接。channel是可以让一个 goroutine 发送特定值到另一个 goroutine 的通信机制。
Go 语言中的通道(channel)是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)的规则,保证收发数据的顺序。每一个通道都是一个具体类型的导管,也就是声明channel的时候需要为其指定元素类型。
声明方式
未初始化的通道类型变量其默认零值是nil
var 变量名称 chan 元素类型
chan:是关键字
元素类型:是指通道中传递元素的类型
例子:
var ch1 chan int // 声明一个传递整型的通道
var ch2 chan bool // 声明一个传递布尔型的通道
var ch3 chan []int // 声明一个传递int切片的通道
推荐声明方式:
make(chan 元素类型, [缓冲大小])
ch4 := make(chan int)//容易报错死锁
ch5 := make(chan bool, 1) // 声明一个缓冲区大小为1的通道
channel操作
通道共有发送(send)、接收(receive)和关闭(close)三种操作。而发送和接收操作都使用<-符号。
ch <- 10 // 把10发送到ch中
x := <- ch // 从ch中接收值并赋值给变量x
<-ch // 从ch中接收值,忽略结果
len(ch) //获取元素数量
cap(ch) //获取元素容量
close(ch)//关闭通道
关闭后的通道有以下特点:
对一个关闭的通道再发送值就会导致 panic。
对一个关闭的通道进行接收会一直获取值直到通道为空。
对一个关闭的并且没有值的通道执行接收操作会得到对应类型的零值。
关闭一个已经关闭的通道会导致 panic。
习题:
package main
import "fmt"
//声明两个 channel ch1 ch2
//声明两个 goroutine f1 f2
//将 1-100 分别放入 ch1 内,然后取出 ch1 的值 并平方 放到 f2 内
func main() {
ch1 := make(chan int, 100)
ch2 := make(chan int, 200)
go f1(ch1)
go f2(ch1, ch2)
for v := range ch2 {
fmt.Println(v)
}
}
func f1(ch1 chan int) {
for i := 0; i < 100; i++ {
ch1 <- i
}
close(ch1)
}
func f2(ch1 chan int, ch2 chan int) {
for {
temp, ok := <-ch1 //temp:从通道中取出的值,如果通道被关闭则返回对应类型的零值。ok:通道ch关闭时返回 false,否则返回 true。
if !ok {
break
}
ch2 <- temp * temp
}
close(ch2)
}
单向通道
在某些场景下我们可能会将通道作为参数在多个任务函数间进行传递,通常我们会选择在不同的任务函数中对通道的使用进行限制,比如限制通道在某个函数中只能执行发送或只能执行接收操作。想象一下,我们现在有Producer和Consumer两个函数,其中Producer函数会返回一个通道,并且会持续将符合条件的数据发送至该通道,并在发送完成后将该通道关闭。而Consumer函数的任务是从通道中接收值进行计算,这两个函数之间通过Processer函数返回的通道进行通信。
<- chan int // 只接收通道,只能接收不能发送
chan <- int // 只发送通道,只能发送不能接收
package main
func main() {
// producerCh := Producer()
// sum := Consumer(producerCh)
// fmt.Println(sum)
var ch3 = make(chan int, 1)
ch3 <- 10
close(ch3)
Consumer(ch3) // 函数传参时将ch3转为单向通道
var ch4 = make(chan int, 1)
ch4 <- 10
var ch5 <-chan int // 声明一个只接收通道ch5
ch5 = ch4 // 变量赋值时将ch4转为单向通道
<-ch5
}
//生产者
func Producer() <-chan int {
maxNum := 10
ch := make(chan int, 10)
for i := 0; i < maxNum; i++ {
if i%2 == 1 {
ch <- i
}
}
close(ch)
return ch
}
//消费者
func Consumer(ch <-chan int) int {
sum := 0
for value := range ch {
sum += value
}
return sum
}
简单的工作池1
package main
import (
"fmt"
"sync"
"time"
)
var wg sync.WaitGroup
func main() {
jobs := make(chan int, 100)
results := make(chan int, 100)
for i := 0; i < 5; i++ {
jobs <- i
}
for j := 0; j < 3; j++ {
go work(j, jobs, results)
}
close(jobs)
for ret := range results {
fmt.Println("结果集:", ret)
}
}
func work(id int, jobs <-chan int, results chan<- int) {
for job := range jobs {
fmt.Printf("id:%d start job %d\n", id, job)
results <- job * 2
time.Sleep(time.Microsecond * 5000)
fmt.Printf("id:%d end job %d\n", id, job)
}
close(results)
}