首页
登录 | 注册

golang基础-chain的使用、range、select

文章目录

      • chan基础使用
      • range获取
      • select多队列

chan基础使用

我们直接来看代码

package main

import (
	"fmt"
	"time"
)

var message = make(chan  string)
func go1(){
	message <- "hello1"
	message <- "hello2"
	message <- "hello3"
	//message <- "hello4"
}
func  go2()  {
	time.Sleep(2*time.Second)
	str:= <-message
	str = str + "go"
	message <- str
}
func main(){
	go go1()
	go go2()
	time.Sleep(3*time.Second)
	fmt.Println(<-message)
	fmt.Println(<-message)
	fmt.Println(<-message)
}

输出结果是

hello1go
hello2
hello3

或者

hello2
hello1go
hello3

或者

hello2
hello3
hello1go

以上代码意思就是:
定义了一个容量为1的chan,在go1里面依次存储3个字符串,但是容量是1,首先会把hello1存入,go1容量占满,挂起阻塞,然后发信号,进行chan通讯,然后2秒后,go2从管道里面取出来,str:= <-message,到这个节点取出完毕,go2会通知go1继续存储,由于管道间通讯需要耗时操作,虽然时间很小,由于cpu性能或者其他的原因,可能g2在发出信号时候,go1还没有进行存储操作,go2里面会把拼接的字符存入,还有可能是go1存储了,然后拼接字符串没有存储,所以在main中,依次取出来会出现3种情况

我们将上述代码改下

var message = make(chan  string,3)

由于容量扩充为3,所以go1会通过fifo方式,依次存入hello1,hello2,hello3,可以理解成队列的先进先出,然后2秒后,go2会取出hello1,取出完毕,管道里面的数据形态是【hello2,hello3,空位】,然后讲取出来的hello1,通过拼接字符串的形式,存入到管道的末尾,【hello2,hello3,hello1go】,然后3秒后,主函数里面就开始依次取出即可,结果如下:

hello2
hello3
hello1go

我们在例2的基础上,再次进行修改,注意是在例2的基础上进行修改,在容量为3的chan里面,在go1里面存入4个字符串,

func go1(){
	message <- "hello1"
	message <- "hello2"
	message <- "hello3"
	message <- "hello4"
}

结果如下:跟例1的原理一样,产生如下的结果

hello2
hello3
hello4

或者

hello2
hello3
hello1go

range获取

以上的demo有几个需要优化的地方
1、我们可以将chan通过参数,传递到go协程里面
2、当我们从chan里面取出数据时,我们可以使用range,而不是一个个的去取
3、chan在为空时候,range依然会去遍历读取数据,我们需要关闭chan
优化后的代码如下:

package main

import (
	"fmt"
	"time"
)


func go1(message chan string){
	message <- "hello1"
	message <- "hello2"
	message <- "hello3"
	message <- "hello4"
}
func  go2(message chan  string)  {
	time.Sleep(2*time.Second)
	str:= <-message
	str = str + "go"
	message <- str
	//关闭chan
	close(message)
}
func main(){
	var message = make(chan  string,3)
	go go1(message)
	go go2(message)
	time.Sleep(3*time.Second)
	//range读取
	for val:=range message{
		fmt.Println(val)
	}
}

如果不关闭chan的话,会报如下的死锁错误信息:
就是协程里面没有存入,主线程里面也无法读取数据,主线程在等协程存入数据,就是互相等待的状态

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan receive]:
main.main()
	E:/golang/gland_pro/flow-statistics/src/test/test.go:29 +0x124

select多队列

package main

import (
	"fmt"
	"strconv"
	"time"
)

func go1(ch chan  string){
	for i:=0;i<5;i++{
		ch <- "hello"+ strconv.Itoa(i)
		//time.Sleep(1*time.Second)
	}
}
func go2(ch chan  int){
	for i:=0;i<5;i++{
		ch <- i
		//time.Sleep(2*time.Second)
	}
}

func main(){
	chan1:=make(chan string,3)
	chan2:=make(chan int,5)

	go go1(chan1)
	go go2(chan2)
	time.Sleep(1*time.Second)
	Loop:
		for {
			select {
			case str,ok:=<-chan1:
				if !ok{
					fmt.Println("ch1 failed")
					continue
				}
				fmt.Println(str)

			case p,oke := <-chan2:
				if !oke{
					fmt.Println("ch2 failed")
					continue
				}
				fmt.Println(p)
			default:
				fmt.Println("ds")
				break Loop
			}
		}

}

结果如下:

0
hello0
1
hello1
hello2
hello3
2
hello4
3
4
ds

Process finished with exit code 0

简单分析下,以上只是一个demo,这个demo有很多不完善的地方
定义了2个协程,然后存入数据,通过select去循环获取多个协程里面的数据,在这里我通过time.Sleep(1*time.Second) 进行主线程阻塞,为了测试效果,一秒钟过后,就循环去读取各个协程里面的数据,如果某个协程里面没有数据了,就终止本地循环,去进行下一次读取,当2个chan都读取完毕了,就进入defalut,就终止循环退出主线程即可

缺陷:1、这里没有用到close(chan)操作,理想的情况下,应该读取完毕某个管道,就应该关闭某个管道,如果网友知道方案,可以留言哦
2、主线程通过阻塞方式,这样就避免了,直接执行for循环的default操作,为了demo效果出此对策



2020 jeepxie.net webmaster#jeepxie.net
10 q. 0.008 s.
京ICP备10005923号