Golang高级编程: 并发篇 (一) 通道 + 通道超时

简介

读取和写通道超时, 是很多人在使用通道过程中经常遇到的, 下面就介绍一下通道,然后简单的对读写通道做超时处理

通道概念 

 1. 通道采用的是FIFO(先进先出)

 2. 通道的创建

    chn := make(chan int, 0) // 创建读写int类型的通道, 0 代表着缓存为0,即写同时必须读取, 否则将堵塞

    readOnlyChn := make(<-chan int) // 只读通道

    writeOnlyChn := make(chan<- int) // 只写通道

3. 关闭通道

    i.  读通道直接返回0(整数类型),bytes则返回[], 

    ii. 写通道 会抛出异常 (send on closed channel)

4. 判断通道是否关闭

   v, ok := <-chn // ok=true说明正常,ok=false说明通道关闭

5. 通道置为nil

    i. 读写通道时都会死锁

    ii. select 前置 nil,则select时直接忽略,select后没有其它的case则同样堵塞

示例

基本使用场景一

package main

import (
	"fmt"
	"time"
)

func recv(chn chan int) {
	for {
		select {
		case v, ok := <-chn:
			fmt.Println("value: ", v, "; ok: ", ok)
			if !ok {
				fmt.Println("channel closed!!! ")
				return // 退出协程
			}
		default:
			fmt.Println("recv alive print.")
			time.Sleep(time.Second * 1)
		}
	}
}


func main() {
	var chn = make(chan int, 0)
	//var readOnlyChn = make(<-chan int)
	//var writeOnlyChn = make(chan<- int)

	/* 先启动接收协程, 在通道写入数据的时候进行读出, 由于之前创建了 0 缓存的通道, 写不同时读出造成堵塞 */
	go recv(chn)

	chn<-1024 // 写入1024数据
	time.Sleep(time.Second * 2) // 延时两秒, 好让接收线程接收处理写入数据

	chn = nil

	//close(chn) // 关闭通道

	time.Sleep(5 * time.Second)
}

上面的使用中, 当通道被置为nil的时候虽然由于加了default不会造成死锁, 但default依然会进行不断的打印执行;

有人说了, 可以在default时判断通道是否为nil, 思路是对的,但是却没那么简单, Go中只有值传递, 即你在传通道这个参数的时候是对通道引用的拷贝, 而不是其本身, 所以通道置nil,其实recv函数内部的chn还是非空的

使用场景二, 一场景升华版

这样, 不管是通道为nil还是关闭通道都能够正常监控到了, 并及时退出协程了

package main

import (
	"fmt"
	"time"
)

func recv(chn *chan int) {
	for {
		select {
		case v, ok := <-*chn:
			fmt.Println("value: ", v, "; ok: ", ok)
			if !ok {
				fmt.Println("channel closed!!! ")
				return // 退出协程
			}
		default:
			if nil == *chn { fmt.Println("channel == nil"); return } 
			fmt.Println("recv alive print.")
			time.Sleep(time.Second * 1)
		}
	}
}


func main() {
	var chn = make(chan int, 0)
	//var readOnlyChn = make(<-chan int)
	//var writeOnlyChn = make(chan<- int)

	/* 先启动接收协程, 在通道写入数据的时候进行读出, 由于之前创建了 0 缓存的通道, 写不同时读出造成堵塞 */
	go recv(&chn)

	chn<-1024 // 写入1024数据
	time.Sleep(time.Second * 2) // 延时两秒, 好让接收线程接收处理写入数据

	chn = nil

	//close(chn) // 关闭通道

	time.Sleep(5 * time.Second)
}

写超时处理一

func main() {
	var chn = make(chan int, 0) // 创建 0 缓存通道

	select {
	case chn<-1024: // 写数据
		fmt.Println("Writed Done!!!")
	case <-time.After(time.Second * 3): // 三秒超时
		fmt.Println("Time out")
	}

}

写超时处理(context.WithTimeout)二

func main() {
	var chn = make(chan int, 0) // 创建 0 缓存通道

	ctx, _ := context.WithTimeout(context.Background(), time.Second * 3)
	select {
	case chn<-1024: // 写数据
		fmt.Println("Writed Done!!!")
	case <-ctx.Done(): // 三秒超时
		fmt.Println("Done: ", ctx.Err())
	}
}

下一篇介绍context

猜你喜欢

转载自blog.csdn.net/halo_hsuh/article/details/107055828