美文网首页
golang通道channel高级

golang通道channel高级

作者: one_zheng | 来源:发表于2018-10-01 15:26 被阅读61次

    单向通道

      only_read := make(<-chan int,1) //只能发送通道
      only_write := make(chan<- int, 1) //只能接受通道

    问:单向通道的用途?
    答:

      单向通道的主要用途就是约束其他代码的行为。

      func SendInt(ch chan<- int) {
          ch <- rand.Intn(1000)
      }
    

     在实际场景中,这种约束一般会出现在接口类型声明中的某个方法定义上。 demo:

    type Notifier interface{
        SendInt(ch chan<- int)
    }
    

     在这里,Notifier接口中的SendInt方法只会接受一个发送通道作为参数,所以,在该接口中的所有实现类型中的SendInt 方法都会受到限制。这种约束方法还是很有用的,尤其是在我们编写模版代码或者可扩展程序库的时候。

      顺便说一下,我们在调用SendInt函数的时候,只需要把一个元素类型匹配的双向通道传给它就行了。没必要用发送通道,因为Go 语言在这种情况下自动地把双向通道转换为函数所需的单向通道。

      intChan1 := make(chan int, 3)
      SendInt(intChan1)
    

      在另一方面,我们还可以在函数声明的结果列表中使用单向通道。如下所示:

      func getIntChan() <-chan int {
        num := 5
        ch := make(chan int, num)
        for i := 0; i< num; i++{
          ch <- i
        }
        close(ch)
        return ch
      }
    

      函数getIntChane会返回一个 <-chan int 类型的通道,这就意味着得到该通道的程序,只能从通道中接受元素值。这实际上就是对函数调用方的一种约束了。

      另外。我们在Go 语言中还可以声明函数类型,如果我们在函数类型中使用了单向通道,那么就相当于在约束所有实现了这个函数类型的函数。

        intChan2 := getIntChane()
        for elem := range intChan2{
              fmt.Printf("The element int intChan2: %v\n", elem)
        }
    

    for range 遍历通道要注意的点:

      1.for 语句会不断尝试从intChan2 中取出元素值,即使intChan2被关闭,它也会在取出所有剩余的元素值之后再结束关闭。
      2.当 intChan2中没有元素值时,它会被阻塞在有 for关键字的那一行,直捣有新的元素可取。
      3.假设intChan2的值为nil,那么它会被永远阻塞在有for关键字的那一行。

    问:select语句与通道联用,应注意什么?
    答:

      

        // 通道数组
        intChannels := [3]chan int {
            make(chan int, 1),
            make(chan int, 1),
            make(chan int, 1),
        }
        // 随机选择一个通道,并向它发送元素子
        index := rand.Int(3)
        fmt.Printf("The index:%d \n",index)
        intChannels[index] <-index
        // 哪一个通道中有可取的元素值,哪个对应的分支就被执行。
        select {
            case<-intChannels[0]:
                fmt.Println("The first candidate case is selected.")
           case<-intChannels[1]:
                fmt.Println("The second candidate case is selected.")
           case<-intChannels[2]:
                fmt.Println("The third candidate case is selected.")
          default:
                fmt.Println("No candidate case is selected")
        }
    

    在使用select语句时,应该注意:
      1. 如果加入了default分支,那么无论涉及通道操作的表达式是否有阻塞,select语句都不会被阻塞。如果那几个表达式被阻塞了,或者说都没有满足求值的条件,那么默认分支就会被选中并执行。
      2. 如果没有加入默认分支,那么一旦所有的case表达式都没有满足求值条件,那么select语句就会被阻塞。直到至少有一个case表达式满足条件为止。
     3. 我们可能会因为通道关闭了,而直接从通道接收到一个其元素类型的零值。所以,在很多时候,我们需要通过接受表达式的第二个结果值来判断通道是否关闭。一旦发现某个通道关闭了,我们就应该及时地屏蔽掉对应的分支或者采取其他措施。这对于程序逻辑和程序性能都是有好处的。
      4. select语句只能对其中的一个每一个case表达式各求值一次。所以,如果我们想连续或定时地操作其中的通道时,就往往需要在for语句中嵌入select语句的方法实现。但这时要注意,简单地在select语句的分支中使用break语句,只能结束当前的select语句的执行,而不会对外层的for语句产生作用。这种错误的用法可能会让这个for语句无休止地运行下去。

    错误示例:
      intChan  := make(chan int, 1)
      // 1秒钟后关闭通道
      time.AfterFunc(time.Second, func(){ 
        close(intChan)
      })
      select {
        case _, ok := <-intChan:
               if !ok {
                  break;//错误,不能跳出for,只能跳出select
              } 
              fmt.Println("The candidate case is selected.")
      }
    
    

    select分支选择规则细节:

      1. select语句候选分支的case表达式 求值顺序是依次从代码的编写顺序从上到下的。排在最上方的候选分支中最左边的表达式会最先被求值,然后是它右边的表达式。然后依次顺序向下的候选分支开始求值。

      2. 进当select语句中的所有case表达式都被求值完毕后,它才会开始选择候选分支。这时候,它只会挑选满足选择条件的候选分支执行。如果所有的候选分支都不满足条件,那么默认分支就会被执行。如果这时没有默认分支,那么select分支语句就会立即进入阻塞状态,直到至少有一个候选分支满足选择条件为止。一旦有一个候选分支满足选择条件,select语句(或者说它所在的goroutine)就会被唤醒,这个候选分支就会被执行。

      3. 如果select语句发现同时有多个候选分支满足选择条件,那么它会用一个伪随机的算法在这些分支中选择一个并执行。注意,即使select语句是在被唤醒时发现的这种情况,也会这么做。

      4. select语句的每次执行,包括case表达式求值和分支选择,都是独立的,不过,至于它的执行是否是并发安全的,就要看其中的case表达式以及分支中,是否包含并发不安全的代码了。


    问题: 1. 如果在select语句中发现某个通道已关闭,那么应该怎样屏蔽它所在的分支?

    回答: 当判断表达式的第二个参数为false时,代表通道已经关闭,这时候将该channel赋值为nil,那么每次从该nil channel接受时就会阻塞,select会忽略阻塞的通道。

    问题: 2.在select语句与for语句联用时,怎么直接退出最外层的for语句?

    回答: 通过定义标签,配合goto或者breal语句能实现在同一个函数内任意跳转,就可以跳出多层循环了。

    相关文章

      网友评论

          本文标题:golang通道channel高级

          本文链接:https://www.haomeiwen.com/subject/yfrnoftx.html