59

golang通道channel高级

 5 years ago
source link: https://studygolang.com/articles/15195?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

单向通道

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK