likes
comments
collection
share

Go 并发编程|单向通道、time包中的通道

作者站长头像
站长
· 阅读数 11

引言

通道,channel,是用于实现goroutine之间的通信的。一个goroutine可以向通道中发送数据,另一条goroutine可以从该通道中获取数据。前几篇文章我们所讲的通道,都是既可以发送数据,也可以读取数据,我们又把这种通道叫做双向通道。

data := <- a // read from channel a  
a <- data // write to channel a

1. 单向通道

单向通道,也就是定向通道。

之前我们学习的通道都是双向通道,我们可以通过这些通道接收或者发送数据。我们也可以创建单向通道,这些通道只能发送或者接收数据。

双向通道,实例代码:

func main()  {
   /*
      双向:
          chan T -->
              chan <- data,写出数据,写
              data <- chan,获取数据,读
      单向:定向
          chan <- T,
              只支持写,
          <- chan T,
              只读
   */
   ch1 := make(chan string) // 双向,可读,可写
   done := make(chan bool)
   go sendData(ch1, done)
   data :=<- ch1 //阻塞
   fmt.Println("子goroutine传来:", data)
   ch1 <- "我是main。。" // 阻塞

   <-done
   fmt.Println("main...over....")
}
//子goroutine-->写数据到ch1通道中
//main goroutine-->从ch1通道中取
func sendData(ch1 chan string, done chan bool)  {
   ch1 <- "我是小明"// 阻塞
   data := <-ch1 // 阻塞
   fmt.Println("传入main goroutine:",data)

   done <- true
}

运行结果:

$ go run duplex.go
子goroutine传来: 我是小明
main goroutine传来: 我是main。。
main...over....

创建仅能发送或者接收数据的通道:

示例代码:

package main

import "fmt"

func main()  {
   /*
      单向:定向
      chan <- T,
          只支持写,
      <- chan T,
          只读

      用于参数传递:
   */
   ch1 := make(chan int)//双向,读,写
   //ch2 := make(chan <- int) // 单向,只写,不能读
   //ch3 := make(<- chan int) //单向,只读,不能写
   //ch1 <- 100
   //data :=<-ch1
   //ch2 <- 1000
   //data := <- ch2
   //fmt.Println(data)
   //  <-ch2 //invalid operation: <-ch2 (receive from send-only type chan<- int)
   //ch3 <- 100
   //  <-ch3
   //  ch3 <- 100 //invalid operation: ch3 <- 100 (send to receive-only type <-chan int)

   //go fun1(ch2)
   go fun1(ch1)
   data:= <- ch1
   fmt.Println("fun1中写出的数据是:",data)

   //fun2(ch3)
   go fun2(ch1)
   ch1 <- 200
   fmt.Println("main。。over。。")
}
//该函数接收,只写的通道
func fun1(ch chan <- int){
   // 函数内部,对于ch只能写数据,不能读数据
   ch <- 100
   fmt.Println("fun1函数结束。。")
}

func fun2(ch <-chan int){
   //函数内部,对于ch只能读数据,不能写数据
   data := <- ch
   fmt.Println("fun2函数,从ch中读取的数据是:",data)
}

运行结果:

$ go run single.go
fun1中写出的数据是: 100
fun2函数,从ch中读取的数据是: 200
main。。over。。

2. time包中的通道相关函数

type Timer

type Timer struct {
    C <-chan Time
    // 内含隐藏或非导出字段
}

Timer类型代表单次时间事件。当Timer到期时,当时的时间会被发送给C,除非Timer是被AfterFunc函数创建的。

主要就是定时器,标准库中的Timer让用户可以定义自己的超时逻辑,尤其是在应对select处理多个channel的超时、单channel读写的超时等情形时尤为方便。

Timer是一次性的时间触发事件,这点与Ticker不同,Ticker是按一定时间间隔持续触发时间事件。

Timer常见的创建方式:

t:= time.NewTimer(d)
t:= time.AfterFunc(d, f)
c:= time.After(d)

虽然说创建方式不同,但是原理是相同的。

Timer有3个要素:

定时时间:就是那个d
触发动作:就是那个f
时间channel: 也就是t.C

2.1 time.NewTimer()

func NewTimer

func NewTimer(d Duration) *Timer

NewTimer创建一个Timer,它会在最少过去时间段d后到期,向其自身的C字段发送当时的时间。

源代码:

// NewTimer creates a new Timer that will send
// the current time on its channel after at least duration d.
func NewTimer(d Duration) *Timer {
    c := make(chan Time, 1)
    t := &Timer{
        C: c,
        r: runtimeTimer{
            when: when(d),
            f:    sendTime,
            arg:  c,
        },
    }
    startTimer(&t.r)
    return t
}

通过源代码我们可以看出,首先创建一个channel,关联的类型为Time,然后创建了一个Timer并返回。

  • 用于在指定的Duration类型时间后调用函数或计算表达式。
  • 如果只是想指定时间之后执行,使用time.Sleep()
  • 使用NewTimer(),可以返回的Timer类型在计时器到期之前,取消该计时器
  • 直到使用<-timer.C发送一个值,该计时器才会过期

示例代码:

func main() {

   /*
      1.func NewTimer(d Duration) *Timer
          创建一个计时器:d时间以后触发,go触发计时器的方法比较特别,就是在计时器的channel中发送值
   */
   //新建一个计时器:timer
   timer := time.NewTimer(3 * time.Second)
   fmt.Printf("%T\n", timer) //*time.Timer
   fmt.Println(time.Now())   //2022-05-17 00:08:47.5033 +0800 CST m=+0.000122432

   //此处在等待channel中的信号,执行此段代码时会阻塞3秒
   ch2 := timer.C     //<-chan time.Time
   fmt.Println(<-ch2) //2022-05-17 00:08:50.503309 +0800 CST m=+3.000108752

}

运行结果:

$ go run newtimer.go
2022-05-17 00:08:47.5033 +0800 CST m=+0.000122432
2022-05-17 00:08:50.503309 +0800 CST m=+3.000108752

2.2 timer.Stop

func (*Timer) Stop

func (t *Timer) Stop() bool

Stop停止Timer的执行。如果停止了t会返回真;如果t已经被停止或者过期了会返回假。Stop不会关闭通道t.C,以避免从该通道读取不正确的成功。 示例代码:

package main

import (
   "fmt"
   "time"
)

func main() {

   //新建计时器,一秒后触发

   timer2 := time.NewTimer(5 * time.Second)

   //新开启一个线程来处理触发后的事件

   go func() {

      //等触发时的信号

      <-timer2.C

      fmt.Println("Timer 2 结束。。")

   }()

   //由于上面的等待信号是在新线程中,所以代码会继续往下执行,停掉计时器

   time.Sleep(3*time.Second)
   stop := timer2.Stop()

   if stop {

      fmt.Println("Timer 2 停止。。")

   }

}

运行结果:

$ go run timerstop.go
Timer 2 停止。。

2.3 time.After()

func After

func After(d Duration) <-chan Time

After会在另一线程经过时间段d后向返回值发送当时的时间。等价于NewTimer(d).C。

源码:

// After waits for the duration to elapse and then sends the current time
// on the returned channel.
// It is equivalent to NewTimer(d).C.
// The underlying Timer is not recovered by the garbage collector
// until the timer fires. If efficiency is a concern, use NewTimer
// instead and call Timer.Stop if the timer is no longer needed.
func After(d Duration) <-chan Time {
    return NewTimer(d).C
}

示例代码:

func main() {

   /*
      func After(d Duration) <-chan Time
          返回一个通道:chan,存储的是d时间间隔后的当前时间。
   */
   ch1 := time.After(3 * time.Second) //3s后
   fmt.Printf("%T\n", ch1) // <-chan time.Time
   fmt.Println(time.Now()) //2022-05-17 00:19:17.87689 +0800 CST m=+0.000099590
   time2 := <-ch1
   fmt.Println(time2) //2022-05-17 00:19:20.877909 +0800 CST m=+3.001095891
}

运行结果:

$ go run timeafter.go
<-chan time.Time
2022-05-17 00:19:17.87689 +0800 CST m=+0.000099590
2022-05-17 00:19:20.877909 +0800 CST m=+3.001095891