golang通道channel高级

one_zheng · · 1654 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

单向通道

  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语句能实现在同一个函数内任意跳转,就可以跳出多层循环了。


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:one_zheng

查看原文:golang通道channel高级

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1654 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传