Golang Channel

Golang Channel

定义

  • channel本质是队列
  • 线程安全, 多goroutine访问时, 不需要加锁,就是说channel本身线程安全
  • channel有类型,一个stringchannel只能存放string

声明

var identifier chan type

  • channel是引用类型

  • channel必须初始化才能写入数据,即make后才能使用

  • identifier <- factor,将一个数据写入到channel

  • num := <- identifier, 可以取出但是不赋值<- identifier

  • channel中可以使用interface{}

  • Close(channel), 关闭通道, 通过通知读取的协程可以不再阻塞, 通过读取的返回值

  • val,ok :=<-flag, 通道关闭后(如果管道内空, 还可以继续读, 默认读取零值),读取返回具体值和是否读到数据, 返回false表示读取到管道已经关闭, 如果没有关闭通道还是处于阻塞

    func main() {
    	var intChan chan int
    	intChan = make(chan int, 3)
    	//intChan =0xc000102080,&intChan = 0xc000006028
    	fmt.Printf("intChan =%v,&intChan = %v \n",intChan,&intChan)
    	//向管道写入数据
    	intChan <- 2
    	num := 911
    	intChan <- num
    	//不同于slice, channel容量固定
    	a := <- intChan
    	fmt.Printf("cap = %v, len = %v \n", cap(intChan),len(intChan))
    	//取出队列头, 如果channel空就会死锁
    	fmt.Println(a)
    	fmt.Printf("cap = %v, len = %v \n", cap(intChan),len(intChan))
    }
    
  • 如果主线程阻塞了, 但是造成阻塞是无意义的, 就会死锁

    //写协程已经结束, 但是主线程一直读取
    func main() {
       go func() {
          for i := 0; i < 5; i++ {
             flagch <- true
          }
       }()
       for true {
          i:=<-flagch
          fmt.Println(i)
       }
    }
    

易错点

func main() {
	channel := make(chan interface{},5)
	channel <- "1"
	channel <- 2
	channel <- Cat{"张三"}
	<-channel
	<-channel
	cat := <-channel
	//编译期不能判别cat的类型, 虽然结果为main.Cat,{张三}
	fmt.Printf("%T,%v\n",cat,cat)
	//这里必需使用类型断言, 因为编译期认为cat是interface{}
	fmt.Printf("%T\n",cat.(Cat).Name)
}

channel的关闭

使用内置函数close可以关闭channel,当channel关闭后,就不能在向channel写数据了,但是任然可以从该channel读数据

func main() {
	channel := make(chan interface{},5)
	channel <- "1"
	channel <- 2
	close(channel)
	channel <- Cat{"张三"}//panic: send on closed channel
	<-channel
	<-channel
}

channel遍历

选用for-range, 使用len()会造成数据不一致, 再遍历时如果没有关闭管道,就会报dead lock

func main() {
	channel := make(chan interface{}, 20)
	for i := 0; i < 20; i++ {
		channel <- i
	}
	close(channel)
	//channel只返回一个, for-range自动取出channel中的值
	for val := range channel {
		fmt.Println(val)
	}
}

注意点

顺序执行/普通线程

  • 如果写入的数量大于cap, 就会dead lock

  • 如果读取的数量大于cap且没有关闭管道,就会dead lock

  • 如果读取的数量大于cap且关闭管道,读取完管道中的数据后,默认读出管道类型的缺省值,和管道中是否有值

    	test := make(chan int, 3)
    	for i := 0; i < 3; i++ {
    		test <- i
    	}
    	close(test)
    	for i := 0; i < 10; i++ {
            //如果管道中的数据取完后,ok置为false
    		val,ok :=<-test
    		fmt.Println(a,b)
    		//time.Sleep(time.Second)
    	}
    

并发执行/协程运行

在协程运行中,会发生阻塞

  • 如果只有写操作同时写入的数量大于cap,管道会导致当前协程阻塞,而不会dead lock,直到有读取的操作

    var(
    	channel = make(chan int,2)
    )
    func write(){
    	fmt.Println("写之前")
    	channel <- 1
    	channel <- 1
    	channel <- 1 //当大于cap时,channel会导致当前协程阻塞
    	fmt.Println("写之后")
    }
    func main() {
    	go write()
    	fmt.Println("主线程")
    	for{
    
    	}
    }
    
  • 如果只有读操作,但是管道内没有可以读取的值,就会导致当前协程阻塞

    func read() {
    	fmt.Println("读取之前")
    	//如果管道中没有值可以读, channel就会导致当前协阻塞
    	x,ok := <- channel
    	fmt.Println(x,ok)l
    	fmt.Println("读取之后")
    }
    func main() {
    	go read()
    	fmt.Println("主线程")
    	for {
    
    	}
    }
    
  • 读写并存, 写操作通过协程,同样会导致写协程阻塞

    var (
    	channel = make(chan int, 2)
    )
    func write(){
    	fmt.Println("写之前")
    	channel <- 1
    	channel <- 1
        channel <- 1
    	channel <- 1 //运行到这里的时候,channel会导致当前协程阻塞
    	fmt.Println("写之后")
    }
    func read() {
    	fmt.Println("读取之前")
    	//<-channel//如果管道中没有值可以读, channel就会导致当前协阻塞
    	x,ok := <- channel
    	fmt.Println(x,ok)
    	fmt.Println("读取之后")
    }
    func main() {
    	go write()
    	read()
    	fmt.Println("主线程")
    	for {
    
    	}
    }
    
  • 读写并存,但是通道未关闭,如果写大于读,写协程阻塞,反之,读协程阻塞

    var (
    	channel = make(chan int, 2)
    )
    func write(){
    	fmt.Println("写之前")
    	channel <- 1
    	channel <- 1
    	channel <- 1
    	channel <- 1 
    	fmt.Println("写之后")
    }
    func read() {
    	for  {
            //当管道中没有值可以读取时,通道就会导致读协程阻塞,并不会到if中
    		x,ok := <- channel
    		fmt.Println(x,ok)
    		if !ok {
    			fmt.Println("break")
    			break
    		}
    	}
    }
    func main() {
    	go write()
    	go read()
    	fmt.Println("主线程")
    	for {
    
    	}
    }
    
  • 读写并存,但是通道关闭,写大于读,写协程阻塞,如果读大于写,运行完后就会退出读写协程

    var (
       channel = make(chan int, 2)
    )
    func write(){
       fmt.Println("写之前")
       channel <- 1
       channel <- 1
       channel <- 1
       channel <- 1 //运行到这里的时候,channel会导致当前协程阻塞
       fmt.Println("写之后")
       close(channel)
    }
    func read() {
       for  {
          //管道关闭后,无法导致当前读协程阻塞,ok == flase 退出读协程
          x,ok := <- channel
          fmt.Println(x,ok)
          if !ok {
             fmt.Println("break")
             break
          }
       }
    }
    func main() {
       go write()
       go read()
       fmt.Println("主线程")
       for {
    
       }
    }
    

例子

var (
	channel = make(chan int, 10)
	flag    = make(chan bool, 1)
)

func writeData() {
	defer close(channel)
	for i := 0; i < 50; i++ {
		 channel <- i
		fmt.Println("写入", i)
	}
}
func readData() {
	defer close(flag)
	for {
		val, ok := <-channel
		if !ok {
			break
		}
		fmt.Println("读取", val)
		//time.Sleep(time.Millisecond * 400)
	}
	//写完后向一个管道内写入标志
	flag <- true
}
func main() {
	 go writeData()
	 readData()
	//通过一个channel阻塞主线程
	for {
		if _, ok := <-flag; ok {
			break
		}
		time.Sleep(time.Second*2)
		break
	}
}

注意事项

  • channel可以声明为只读, 或者只写, 默认双向通信

    func main() {
    	//声明管道为只写
    	var chan1 chan<- int
    	chan1 = make(chan int, 5)
    	chan1 <- 1
    	//声明管道为只读
    	var chan2 <-chan int
    	chan2 = make(chan int,5)
    	<- chan2
    }
    

    案例

    var (
    	flag = false
    )
    
    //声明管道为只写
    func send(ch chan<- int) {
    	defer close(ch)
    	for i := 0; i < 5; i++ {
    		ch <- i
    	}
    }
    //声明管道为只读
    func read(ch <-chan int) {
    	for {
    		if val, ok := <-ch; ok {
    			fmt.Println(val)
    		} else {
    			flag = true
    			break
    		}
    	}
    }
    func main() {
    	ch := make(chan int, 10)
    	go send(ch)
    	go read(ch)
    	for !flag {
    
    	}
    }
    
  • 使用select可以解决管道数据阻塞

    func main() {
    	intch := make(chan int, 10)
    	for i := 0; i < 10; i++ {
    		intch <- i
    	}
    	strch := make(chan string, 5)
    	for i := 10; i < 15; i++ {
    		strch <- string(i)
    	}
    	label:
    	for  {
    		select {
    		// 如果管道没有关闭,继续取值,不会一直阻塞, 会自动到下一个case匹配
    		case v := <-intch:
    			fmt.Println("intch读取数据", v)
    		case v := <-intch:
    			fmt.Println("str读取数据", v)
    		default:
    			fmt.Println("没有匹配的值")
    			break label
    		}
    	}
    }
    
  • 使用recover, 解决协程中出现panic

    func error() {
    	defer func() {
    		if err:=recover();err !=nil {
    			fmt.Println("协程发生错误",err)
    		}
    	}()
    	slice := []int{}
    	slice[0] = 1
    }
    func correct(){
    	for i := 0; i < 20; i++ {
    		fmt.Println("hello world")
    	}
    }
    func main() {
    	go correct()
    	go error()
    	for {
    
    	}
    }
    

生产消费者模型

var (
	wharehouse chan int = make(chan int, 1)
	flag bool = true
)

func main() {
	go func() {
		defer close(wharehouse)
		for i := 0; i < 10; i++ {
			//需要在生产之前输出
			fmt.Println("生产", i)
			wharehouse <- i
			//这里需要休眠, 因为有可能生产消费后没有打印消费, 然后生产协程直接抢到cpu
			time.Sleep(time.Second)
		}
	}()
	go func() {
		for true {
			if val,ok:=<-wharehouse;ok {
				fmt.Println("消费", val)
				time.Sleep(time.Second)
			}else{
				flag = false
				break
			}
		}
	}()
	for flag {
		;
	}
}
posted @ 2020-08-15 00:43  CyberPelican  阅读(143)  评论(0编辑  收藏  举报