chan通道使用
不要通过共享内存来通信,而是要通过通信来共享内存
- 解决问题
0、解决共享资源的竞争状态,可以原子锁或者互斥锁,还可以使用使用通道更优雅处理。
0、通过使用通道,可以实现多个goroutine直接共享资源。
基本使用
chan是golang语言的关键字,chan T,声明通道类型。
ch := make(chan int)
初始化有2种方式
// 无缓冲通道(阻塞通道)
ch := make(chan int)
// 有缓冲通道(非阻塞通道)
ch2 := make(chan int,1)
无缓冲通道(阻塞通道)
是指在接受前没有任何能力保存值的通道,要求发送和接收的goroutine同时准备好,才能完成发送和接收,否则就会产生死锁。
- 错误使用
func main() {
ch := make(chan int)
ch <- 1
fmt.Println("hello")
}
// fatal error: all goroutines are asleep - deadlock!
- 正常使用
func main() {
ch := make(chan int)
go func() { ch <- 1 }()
fmt.Println("hello", <-ch)
}
// hello 1
有缓冲通道(非阻塞)
是指在被接收前能够存储一个或者多个值的通道,并不会要求发送和接收同时准备好。
func main() {
ch := make(chan int, 10)
ch <- 1
ch <- 2
fmt.Println(<-ch)
}
// 1
两者的最大区别就是,在被接收前一个不能存值,一个能存值,无缓冲通道能够保证在同一时间内进行数据交换。有缓冲通道不能保证,因为两者的阻塞时机都不一样。
for和select结合使用
- 可以使用 for 循环读出chan中的数据
func main() {
ch := make(chan int, 10)
go func() {
for i := 1; i < 10; i++ {
ch <- i
}
close(ch) // 避免下面for,一致阻塞读取导致死锁,
}()
for val := range ch {
fmt.Print(val)
}
fmt.Println(" end。。。")
}
// 123456789 end。。。
- select可以随机读取多个通道,
func main() {
ch := make(chan string, 10)
ch2 := make(chan string, 10)
for {
select {
case val1 := <-ch:
fmt.Println("ch-val1", val1)
case val2 := <-ch2:
fmt.Println("ch2-val2", val2)
case <-time.NewTimer(time.Second).C:
ch <- time.Now().String()
ch2 <- time.Now().String()
}
}
}
// ch 和 ch2随机读,time.NewTimer(time.Second)创建一个超时器,超过1s则往ch中写新内容
单向通道
避免通道的的写入和读取误用在错误的场景,所以提供了单方向的通道类型,单向通道有利于代码接口的严谨性
func main() {
// 双向通道
ch := make(chan int)
// 写通道
writeCh := make(chan<- int)
// 读通道
readCh := make(<-chan int)
}
双向通道可以转换为 单项通道,反之则不能。
单向通道的使用
type Timer struct {
C <-chan Time
r runtimeTimer
}
<-time.NewTimer(time.Second).C
close通道
通道关闭会通知所有正在读取通道的协成,
在实践中,并不需要关心是否所有通道已经关闭,当通道没有被引用是,将会被golang的垃圾自动回收器回收。
close(chan)
生产者和消费者
package main
import (
"fmt"
"math/rand"
"sync"
"time"
)
var ch = make(chan string, 10)
var wg sync.WaitGroup
var isClose bool // 模拟程序关闭-设置 isClose 并退出生产者
func main() {
go producer("p1")
go producer("p2")
go consumer("c1")
time.Sleep(10 * time.Second)
isClose = true
close(ch)
wg.Wait() // 等待所有的消费者消费完毕,消息则退出应用程序
}
func producer(name string) {
for i := 1; i < 5; i++ {
fmt.Printf("producer[%s]--%d \n", name, i)
if isClose { // 通道chan已经关闭了
return
}
time.Sleep(time.Duration(rand.Intn(100)) * time.Millisecond) // 模拟消费者消费速度
ch <- fmt.Sprintf("producer[%s]-%d", name, i)
}
}
func consumer(name string) {
wg.Add(1)
for val := range ch {
time.Sleep(500 * time.Millisecond) // 模拟消费者消费速度
fmt.Printf("consumer[%s]--%s \n", name, val)
}
wg.Done()
}
Q&A
通道那些操作导致painc
- 关闭一个已经关闭的通道,将会触发panic
func main() {
ch := make(chan int, 2)
close(ch)
close(ch)
}
- 关闭一个没有初始化的通道,将会触发painc
func main() {
var ch chan int
close(ch)
}
- 向已经关闭的通道写数据,将会触发painc
func main() {
ch := make(chan int, 2)
close(ch)
ch <- 1
}
make chan
- make(chan int) 和 make(chan int,0) 的性质都是一样的,都是无缓冲通道
- len 和cap ,len-chan中的现有元素个数是多少,cap-chan中的容量是多大