Channel 使用事项和注意细节

发布时间:2023年12月31日

(1)channel 可以声明为只读,或者只写性质
(2)channel 只读和只写的最佳实践案例

在默认情况下,管道是双向管道,即可读可写。

var ch chan int

func main() {
	//声明为只写管道
	var chan1 chan<- int
	chan1 = make(chan int, 3)
	fmt.Println(chan1)

	//声明为只读
	var chan2 <-chan int
	chan2 = make(chan int, 3)
	fmt.Println(chan2)
}

下面是只读,只写管道,可以有效的预防我们误操作。相当于将可读可写管道将只读的功能封装起来了,不给你使用,但是类型依然是chan int类型。

?

?

?select + channel? 示例


使用select可以解决从管道取数据的阻塞问题【案例演示】

假设要从网上下载一个文件,我启动了 3 个 goroutine 进行下载,并把结果发送到 3 个 channel 中。其中,哪个先下载好,就会使用哪个 channel 的结果。

在这种情况下,如果我们尝试获取第一个 channel 的结果,程序就会被阻塞,无法获取剩下两个 channel 的结果,也无法判断哪个先下载好。这个时候就需要用到多路复用操作了,在 Go 语言中,通过 select 语句可以实现多路复用,其语句格式如下:

select {
case i1 = <-c1:
     //todo
case c2 <- i2:
	//todo
default:
	// default todo
}

整体结构和 switch 非常像,都有 case 和 default,只不过 select 的 case 是一个个可以操作的 channel。

小提示:多路复用可以简单地理解为,N 个 channel 中,任意一个 channel 有数据产生,select 都可以监听到,然后执行相应的分支,接收数据并处理。

有了 select 语句,就可以实现下载的例子了,如下面的代码所示:

func main() {

   //声明三个存放结果的channel
   firstCh := make(chan string)
   secondCh := make(chan string)
   threeCh := make(chan string)

   //同时开启3个goroutine下载
   go func() {
      firstCh <- downloadFile("firstCh")
   }()

   go func() {
      secondCh <- downloadFile("secondCh")
   }()

   go func() {
      threeCh <- downloadFile("threeCh")
   }()

   //开始select多路复用,哪个channel能获取到值,就说明哪个最先下载好,就用哪个。

   select {
       case filePath := <-firstCh:
       fmt.Println(filePath)

       case filePath := <-secondCh:
       fmt.Println(filePath)

       case filePath := <-threeCh:
       fmt.Println(filePath)
   }
}

func downloadFile(chanName string) string {
   //模拟下载文件,可以自己随机time.Sleep点时间试试
   time.Sleep(time.Second)
   return chanName+":filePath"
}

如果这些 case 中有一个可以执行,select 语句会选择该 case 执行,如果同时有多个 case 可以被执行,则随机选择一个,这样每个 case 都有平等的被执行的机会。如果一个 select 没有任何 case,那么它会一直等待下去。

---------------------------------------------------------------------------------------------------------------------------

要从管道当中读取数据需要close这个管道,不去close这个管道去遍历的时候,管道会阻塞,如果阻塞在main协程里面会发生死锁。

package main

import (
	"fmt"
	"time"
)

func main() {
	//使用select可以解决从管道当中读取数据阻塞的问题

	//1.定义一个管道 10个数据int
	initChan := make(chan int, 10)
	for i := 0; i < 5; i++ {
		initChan <- i
	}
	//2.定义一个管道 5个数据string
	stringChan := make(chan string, 5)
	for i := 0; i < 5; i++ {
		stringChan <- "hello" + fmt.Sprintf("%d", i)
	}

	//传统的方法在遍历管道的时候如果不关闭会阻塞导致死锁
	//在实际开发过程中,可能无法确定什么时候关闭该管道
	//可以使用select的方式可以解决

	for {
		select {
		//如果管道一直没有关闭,也不会一直阻塞在这里导致死锁
		// 它会自动的到下一个case匹配,不依赖关闭
		case v := <-initChan:
			time.Sleep(time.Second * 1)
			fmt.Printf("从intchan读取了数据%d\n", v)
		case v := <-stringChan:
			time.Sleep(time.Second * 1)
			fmt.Printf("从stringChan读取了数据%s\n", v)
		default:
			time.Sleep(time.Second * 1)
			fmt.Println("都取不到了,不玩了,这里可以加入业务逻辑")
		}
	}
}



从intchan读取了数据0
从intchan读取了数据1
从intchan读取了数据2
从intchan读取了数据3
从intchan读取了数据4
从stringChan读取了数据hello0
从stringChan读取了数据hello1
从stringChan读取了数据hello2
从stringChan读取了数据hello3
从stringChan读取了数据hello4
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑
都取不到了,不玩了,这里可以加入业务逻辑

如果是在函数里面,直接使用return,那么就直接退出协程的函数。


4)goroutine中使用recover,解决协程中出现panic,导致程序崩溃问题.【案例演示】
说明:如果我们起了一个协程,但是这个协程出现了panic,如果我们没有捕获这个panic,就会造成整个程序崩溃,这时我们可以在goroutine中使用recover来捕获panic,进行处理,这样即使这个协程发生的问题,但是主var mapl map[string]string线程仍然不受影响,可以继续执行。map1["nol”]="tom"
?

文章来源:https://blog.csdn.net/qq_34556414/article/details/135302585
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。