Go语言学习笔记10——Channel
Channel:goroutine和goroutine之间双向的通道
基本语法
- 创建int类型的channel
c := make(chan int)
- 发送数据
c <- 1
- 接受数据
n := <-c
func chanDemo() {
//创建int类型的channel
c := make(chan int)
//接收数据
go func() {
for {
n := <-c
fmt.Println(n)
}
}()
//发送数据
c <- 1
c <- 2
}
channel是一等公民,可以作为参数或返回值
//chan<- int 只能向channel发数据
//<-chan int 只能从channel收数据
func createWorker(id int) chan<- int {
//发数据
c := make(chan int)
go worker(id, c)
return c
}
func chanDemo() {
//创建10个channel分发给10个worker
var channels [10]chan<- int
for i := 0; i < 10; i++ {
channels[i] = createWorker(i)
}
//给10个channel分发数据
for i := 0; i < 10; i++ {
channels[i] <- 'a' + i
}
for i := 0; i < 10; i++ {
channels[i] <- 'A' + i
}
time.Sleep(time.Millisecond)
}
Channel缓冲区
func bufferedChannel() {
//发数据必须有人收,否则会deadlock
//缓冲区大小设为3,只有在发送三个以上数据时才会出现deadlock
c := make(chan int, 3)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
time.Sleep(time.Millisecond)
}
channel的close
func worker(id int, c chan int) {
//读完channel内的数据就退出的两种方法
for n := range c {
fmt.Printf("Worker %d received %c\n", id, n)
}
for {
n, ok := <-c //如果没有值了ok为false
if !ok {
break
}
fmt.Printf("Worker %d received %c\n", id, n)
}
}
func channelClose() {
c := make(chan int)
go worker(0, c)
c <- 'a'
c <- 'b'
c <- 'c'
c <- 'd'
//发送方可以close
//接收方有两种判断方法 ok,range
close(c) //结束后依旧会接收到数据——(channel具体类型的零值)
time.Sleep(time.Millisecond)
}
使用channel等待goroutine的结束
一个死锁的例子
channel的发送和接收都是阻塞式的
dowork中打印完之后给done发送了一个true,此时等待接受这个true
而接受true在分别向in发送完大小写字母之后
第一次打印小写字母可以正常打印,但是打印完之后要等待true被接收,此时继续打印大写字母便会进入循环等待
type worker struct {
in chan int
done chan bool
}
func doWork(id int, c chan int, done chan bool) {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
done <- true
}
}
func createWorker(id int) worker {
w := worker{
in: make(chan int),
done: make(chan bool),
}
go doWork(id, w.in, w.done)
return w
}
//所有channel的发送的都是阻塞式的
func chanDemo() {
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i)
}
for i, worker := range workers{
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}
for _, worker := range workers {
<-worker.done
<-worker.done
}
}
解决方法:
func doWork(id int, c chan int, done chan bool) {
for {
fmt.Printf("Worker %d received %c\n", id, <-c)
go func() {
done <- true }()
}
}
WaitGroup的使用
type worker struct {
in chan int
done func()
}
func doWork(id int, w worker) {
for n := range w.in {
fmt.Printf("Worker %d received %c\n", id, n)
w.done()
}
}
func createWorker(id int, wg *sync.WaitGroup) worker {
w := worker{
in: make(chan int),
done: func() {
wg.Done()
},
}
go doWork(id, w)
return w
}
//所有channel的发送的都是阻塞式的
func chanDemo() {
var wg sync.WaitGroup
var workers [10]worker
for i := 0; i < 10; i++ {
workers[i] = createWorker(i, &wg)
}
wg.Add(20)
for i, worker := range workers {
worker.in <- 'a' + i
}
for i, worker := range workers {
worker.in <- 'A' + i
}
wg.Wait()
}
Select
select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。
select 随机执行一个可运行的 case。如果没有 case 可运行,它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。
- 每个 case 都必须是一个通信
- 所有 channel 表达式都会被求值
- 所有被发送的表达式都会被求值
- 如果任意某个通信可以进行,它就执行,其他被忽略。
如果有多个 case 都可以运行,Select 会随机公平地选出一个执行。其他不会执行。
否则:
如果有 default 子句,则执行该语句。
如果没有 default 子句,select 将阻塞,直到某个通信可以运行;Go 不会重新对 channel 或值进行求值。
在Select中可以使用Nil Channel,当数据还没准备好的时候可以把Channel置为nil,这样case就不会执行
示例
package main
import (
"fmt"
"math/rand"
"time"
)
func generator() chan int {
out := make(chan int)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(1500)) * time.Millisecond)
out <- i
i++
}
}()
return out
}
func worker(id int, c chan int) {
for n := range c {
time.Sleep(time.Second)
fmt.Printf("Worker %d received %d\n", id, n)
}
}
func createWorker(id int) chan<- int {
c := make(chan int)
go worker(id, c)
return c
}
func main() {
var c1, c2 chan int = generator(), generator()
worker := createWorker(0)
var values []int
tm := time.After(10 * time.Second)
tick := time.Tick(time.Second)
for {
var activeWorker chan<- int
var activeValue int
//values中存有数据,对activeWorker初始化
if len(values) > 0 {
activeWorker = worker
activeValue = values[0]
}
select {
case n := <-c1:
values = append(values, n)
case n := <-c2:
values = append(values, n)
//activeWorker没有值的时候为nil,此时阻塞不会执行
case activeWorker <- activeValue:
values = values[1:]
//相邻两个请求之间超过800ms即select阻塞时间超过800ms,则输出timeout
case <-time.After(800 * time.Millisecond):
fmt.Println("timeout")
//每隔一秒输出长度
case <-tick:
fmt.Println("queue len =", len(values))
//总时间10s后退出
case <-tm:
fmt.Println("bye")
return
}
}
}
time.After():是本次监听动作的超时时间, 意思就说,只有在本次select 操作中会有效, 再次select 又会重新开始计时
传统的同步机制
- WaitGroup
- Mutex
- Cond
示例
package main
import (
"fmt"
"sync"
"time"
)
type atomicInt struct {
value int
lock sync.Mutex
}
func (a *atomicInt) increment() {
fmt.Println("safe increment")
func() {
a.lock.Lock()
defer a.lock.Unlock()
a.value++
}()
}
func (a *atomicInt) get() int {
a.lock.Lock()
defer a.lock.Unlock()
return a.value
}
func main() {
var a atomicInt
a.increment()
go func() {
a.increment()
}()
time.Sleep(time.Millisecond)
fmt.Println(a.get())
}
并发编程模式
- 生成器,可以将其抽象为服务/任务
- 同时等待多个服务:两种方法
- select:知道channel的具体数量
- 给每个channel开一个goroutine:不知道channel的具体数量(注意循环变量的坑,通过函数传参进行拷贝来解决)
示例
package main
import (
"fmt"
"math/rand"
"time"
)
func msgGen(name string) chan string {
c := make(chan string)
go func() {
i := 0
for {
time.Sleep(time.Duration(rand.Intn(2000)) * time.Millisecond)
c <- fmt.Sprintf("service %s message %d", name, i)
i++
}
}()
return c
}
//不知道有多少个channel的时候用这种fanIn
func fanIn(chs ...chan string) chan string {
c := make(chan string)
for _, ch := range chs {
//如果直接用ch的话,ch只有一个值,只会取最后一个channel的值送给c
//所以要通过一个参数拷贝ch进行传递
go func(in chan string) {
for {
c <- <-in
}
}(ch)
}
return c
}
//明确channel个数时用select
func fanInBySelect(c1, c2 chan string) chan string {
c := make(chan string)
go func() {
for {
select {
case m := <-c1:
c <- m
case m := <-c2:
c <- m
}
}
}()
return c
}
func main() {
m1 := msgGen("service1")
m2 := msgGen("service2")
m3 := msgGen("service3")
m := fanIn(m1, m2, m3)
for {
fmt.Println(<-m)
}
}
任务的控制
- 非阻塞等待
- 超时机制
- 任务中断/退出
- 优雅退出
示例
package main
import (
"fmt"
"math/rand"
"time"
)
func msgGen(name string, done chan struct{
}) chan string {
c := make(chan string)
go func() {
i := 0
for {
select {
case <-time.After(time.Duration(rand.Intn(5000)) * time.Millisecond):
c <- fmt.Sprintf("service %s message %d", name, i)
case <-done:
fmt.Println("cleaning up")
time.Sleep(2 * time.Second)
fmt.Println("cleanup done")
done<- struct{
}{
}//双向通信,对方收到done才退出
return
}
}
}()
return c
}
//不知道有多少个channel的时候用这种fanIn
func fanIn(chs ...chan string) chan string {
c := make(chan string)
for _, ch := range chs {
//如果直接用ch的话,ch只有一个值,只会取最后一个channel的值送给c
//所以要通过一个参数拷贝ch进行传递
go func(in chan string) {
for {
c <- <-in
}
}(ch)
}
return c
}
//明确channel个数时用select
func fanInBySelect(c1, c2 chan string) chan string {
c := make(chan string)
go func() {
for {
select {
case m := <-c1:
c <- m
case m := <-c2:
c <- m
}
}
}()
return c
}
//非阻塞等待
//等到返回true,没等到返回false
func nonBlockingWait(c chan string) (string, bool) {
select {
case m := <-c:
return m, true
default: //一旦阻塞就进入default
return "", false
}
}
//超时机制
func timeoutWait(c chan string, timeout time.Duration) (string, bool) {
select {
case m := <-c:
return m, true
case <-time.After(timeout):
return "", false
}
}
func main() {
done := make(chan struct{
})
m1 := msgGen("service1", done)
for i := 0; i < 5; i++ {
if m, ok := timeoutWait(m1, time.Second); ok {
fmt.Println(m)
} else {
fmt.Println("timeout")
}
}
done <- struct{
}{
}
<-done
}