学习Go语言已有4个月,在此对Go语言相关知识点做个总结
文章目录
一. Go中的Channel是什么?
Go语言的并发模型是CSP(Communicating Sequential Processes)
,提倡通过通信共享内存而不是通过共享内存而实现通信。
Channel
是一种特殊的类型。通道像一个传送带或者队列,总是遵循先入先出(First In First Out)
的规则,保证收发数据的顺序,可以类比成Unix
中的进程的通信方式管道。
使用channel
可以方便地在各goroutine
之间传递数据,保证了各协程的通信。
二. Channel语法
1.定义和创建Channel
var ch chan int //定义一个int类型的channel
var ch = make(chan string, 5) //创建一个传递string类型的channel,缓冲为5
2.操作Channel
ch := make(chan int, 5) //先定义一个传递int类型的缓冲为5的channel
ch <- 8 //将8发送到ch中
a := <- ch //用变量a来接收
<- ch //从ch中接受,忽略结果
close(ch) // 关闭channel
对于关闭channel需要注意:
- 对关闭后的
channel
进行发送数据操作会导致panic
- 对关闭后的
channel
进行读数据操作会一直接收值,直到channel
中无数据 - 对一个已关闭的无数据的
channel
进行读操作会接收到对应类型的0值,如int类型接收到0
,string
类型接收到""
- 关闭一个已经关闭的
channel
会导致panic
3.Channel的缓冲
Channel分为有缓冲和无缓冲两种
- 无缓冲:
ch := make(chan int)
ch <- 8
fmt.Println("发送数据成功")
上面代码执行结果会报错,不会打印"发送数据成功"
,因为ch通道没有缓冲,对其发送数据后,协程便会一直阻塞在此,除非另一个协程对ch进行读取操作,才会继续执行后面的代码
- 有缓冲
ch := make(chan int, 2) // ch缓冲为2
一般使用for range
结构对channel
进行读取:
ch := make(chan int, 3) // 定义一个缓冲为3的channel
for i := 0; i < 3; i++ {
ch <- i //向ch中连续发送3个数据
}
close(ch) // 别忘了关闭channel
// 使用for range读取channel,当channel中数据被读完后,会自动退出for循环
for v := range ch {
fmt.Println(v) // 打印结果分别为 0 1 2
}
// 再对已关闭的ch进行直接读取
fmt.Println(<- ch) // 打印结果为 0,即int类型对应的零值
//再次对ch进行读取
a, ok := <- ch
if ok {
fmt.Println(a)
} else {
fmt.Println("接收失败,channel已经关闭") // 会打印该条语句,因为ch已经关闭
}
//此时再对ch发送数据
ch <- 8 // 会报错:panic: send on closed channel
4. 单向Channel和双向Channel
其实channel
都是双向的,单向channel
主要用于方法声明,防止channel
被滥用。
一个单独存在
的单向channel
是毫无意义
的。
示例代码:
func example(ch <- chan int){ //传入channel,channel在该函数中只能向外发送数据
for {
select{
case <- ch:
fmt.Println("你好")
}
}
ch <- 1 // 会报错
}
func main() {
ch := make(chan int, 1) //定义一个双向channel
example(ch)
}
三. 使用场景
1. 用于goroutine之间的消息传递:
func work(ch chan struct{}){
fmt.Println("任务执行中......")
time.Sleep(1*time.Second)
ch <- struct{}{} // 传递消息给通道
}
func main() {
ch := make(chan struct{})
go work(ch)
<- ch // 阻塞等待消息
fmt.Println("任务完成!")
}
输出结果:
任务执行中......
任务完成!
进程 已完成,退出代码为 0
2. 用于goroutine之间数据传递
如交替打印数字和字母:
func num(ch chan int,wg *sync.WaitGroup) {
for i := range ch {
fmt.Printf("%d%d",i,i+1)
ch <- i + 1
wg.Done()
}
}
func char(ch chan int,wg *sync.WaitGroup) {
for i := range ch {
fmt.Print(string(63+i), string(64+i))
ch <- i + 1
wg.Done()
}
}
func main() {
ch := make(chan int)
wg := &sync.WaitGroup{}
wg.Add(26)
go num(ch,wg)
ch <- 1
go char(ch,wg)
wg.Wait()
}
3. 控制并发数量
func main() {
jobCount := 38
wg := sync.WaitGroup{}
wg.Add(jobCount)
limit := make(chan struct{},10) // 并发数量限制为10
for i := 0; i < jobCount; i++ {
limit <- struct{}{}
go func() {
fmt.Println("任务执行中")
time.Sleep(1*time.Second)
<- limit
wg.Done()
}()
}
wg.Wait()
fmt.Println("任务完成")
}
4. 超时控制
func work(ch chan struct{}) <-chan struct{} {
go func() {
fmt.Println("开始任务")
time.Sleep(3 * time.Second)
ch <- struct{}{}
}()
return ch
}
func main() {
var ch chan struct{}
select {
case <-work(ch):
fmt.Println("任务准时完成")
case <-time.After(1 * time.Second):
fmt.Println("任务超时")
}
}
四. Channel注意事项
1.len和cap的区别
len
指channel
中已有的数据长度,cap
指channel
定义时的容量,即缓冲
ch1 := make(chan int, 3)
ch <- 9
ch <- 6
fmt.Println(len(ch1)) // 输出 2
fmt.Println(cap(ch1)) // 输出 3
ch2 := make(chan int)
fmt.Println(cap(ch2)) // 输出 0
2.可能存在的危险
1.使用channel不当造成阻塞
示例一:
func main() {
ch := make(chan int)
ch <- 1
fmt.Println(<- ch)
}
运行报错fatal error: all goroutines are asleep - deadlock!
因为ch是无缓冲channel,当c接收了值后,必须要有另一个goroutine对ch进行读取,程序才能进行下去,否则阻塞。
示例二:
func main() {
ch := make(chan int, 1)
ch <- 1
ch <- 2
fmt.Println(<- ch)
}
该情况也会运行报错,因为原因同上
2. channel未关闭
func main() {
ch := make(chan int,2)
go func(ch chan int){
ch <- 1
ch <- 3
}(ch)
for v := range ch {
fmt.Println(v)
}
}
报错fatal error: all goroutines are asleep - deadlock!
channel未关闭导致的错误,应该在向channel发送数据完成后对其用close()
函数进行关闭