• 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 
      ",intChan,&intChan)
      	//向管道写入数据
      	intChan <- 2
      	num := 911
      	intChan <- num
      	//不同于slice, channel容量固定
      	a := <- intChan
      	fmt.Printf("cap = %v, len = %v 
      ", cap(intChan),len(intChan))
      	//取出队列头, 如果channel空就会死锁
      	fmt.Println(a)
      	fmt.Printf("cap = %v, len = %v 
      ", 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
    ",cat,cat)
    	//这里必需使用类型断言, 因为编译期认为cat是interface{}
    	fmt.Printf("%T
    ",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 {
    		;
    	}
    }
    
  • 相关阅读:
    centos基于.net的第一个asp项目
    centos创建第一个 .NET app
    centos搭建.net3.1环境
    ASP.NET Core 的 Docker 映像
    centos+python2+django+nginx+uwsgi环境搭建
    centos+python2+flask+nginx+uwsgi环境搭建
    centos+python2+apache2+flask环境搭建
    小程序字体转换
    小程序播放语音之wx.createInnerAudioContext()
    小程序隐藏scroll-view滚动条的实现
  • 原文地址:https://www.cnblogs.com/kikochz/p/13507218.html
Copyright © 2020-2023  润新知