Go并发编程3-Channel通道


不同于传统的多线程并发模型使用共享内存来实现线程间通信的方式,golang 的哲学是通过 channel 进行协程(goroutine)之间的通信来实现数据共享:

Do not communicate by sharing memory; instead, share memory by communicating.

这种方式的优点是通过提供原子的通信原语,避免了竞态情形(race condition)下复杂的锁机制
channel 可以看成一个FIFO 队列,对 FIFO 队列的读写都是原子操作,不需要加锁。对 channel 的操作行为结果总结如下:

操作nil channelclosed channelnot-closed non-nil channel
closepanicpanic成功 close
写 ch <-一直阻塞panic阻塞或成功写入数据
读 <- ch一直阻塞读取对应类型零值阻塞或成功读取数据

1. 非缓冲信道

  • 通道变量没有字面量,必须使用make创建。
  • 非缓冲信道就是缓冲大小为0的通道
  • 生产过量死锁,消费过量会阻塞
  • channel可以接收**一个或两个返回参数**。
  • range-close(推荐):以阻塞的方式遍历通道。
  • 为了避免读到closed的channel的零值,建议使用,可以使用v,ok := <-ch接收值。
func main() {
   withRev()
   nobufFoo()
   nobufBar()
}

func withRev() {
   var ch = make(chan int)
   go func() {
      ch <- 1
   }()
   ret, ok := <-ch
   //ret= <-ch //接收一个或两个返回参数。
   fmt.Println(ret, ok)
}

// 非缓冲信道: 就是缓冲大小为0的通道
func nobufFoo() {
   ch := make(chan int)
   go func() {
      fmt.Println(<-ch)
   }()

   //main作为生产者
   ch <- 1
   time.Sleep(time.Second)
}

// range-close:以阻塞的方式遍历通道
func nobufBar() {
   ch := make(chan string)
   go func() {
      ch <- "A"
      ch <- "B"
      close(ch)
   }()

   //main作为消费者
   for c := range ch {
      fmt.Println(c)
   }
}

2. 缓冲信道

  • 缓冲信道,超容或未关闭都会引发死锁异常
// 缓冲通道是非阻塞通道
func main() {
   ch := make(chan string, 3)
   ch <- "A"
   ch <- "B"
   // 避免生产过量
   close(ch)

   for val := range ch {
      fmt.Println(val)
   }
}

3. 单向信道

// 生产者
func producer(c chan<- int) {
   for i := 0; i < 10; i++ {
      fmt.Println("send:", i)
      c <- i
   }
   close(c)
}

// 消费者
func consumer(c <-chan int) {
   for v := range c {
      fmt.Println("receive:", v)
   }
}

// 单项通道用于约束参数,双向通道是通用通道
func main() {
   ch := make(chan int)    //1.阻塞式
   ch = make(chan int, 10) //2.非阻塞式
   go producer(ch)
   go consumer(ch)

   var quit string
   fmt.Scanln(&quit)
}

4. 多路复用

  • select就是用来监听和channel有关的IO操作(包括关闭)
  • 用来监听Goroutine下的多个Channel,每个case必须是一个IO操作
  • 多个Channel都无数据时,Select会一直等待到其中一个有数据为止。
  • 多个Channel都有数据时,Select会随机选择一个case执行。
  • 多个Channel都无数据时,且default子语句存在时,default会被执行
  • 持续监听多个Channel,可用for语句协助。
  • Select可以在接收端监听,也可以在发送端监听。
  • 尽量避免default而引发的循环消耗资源,
// select-default是非阻塞模式,应尽量避免

// 监听【收、发、关】
func main() {
   ListenClose()
   Generator()
}

// 监听close
func ListenClose() {
   ch := make(chan struct{})
   go func() {
      time.Sleep(time.Second * 3)
      close(ch)
   }()

   select {
   case <-ch:
      fmt.Println("closed...")
   }
}

// 一个channel就是一个被激活的通道信号发生器
// channel发生器
func Generator() {
   timeBeat := func() chan int {
      out := make(chan int)
      go func() {
         for {
            time.Sleep(time.Second)
            out <- time.Now().Second()
         }
      }()
      return out
   }

   ch1, ch2 := timeBeat(), timeBeat()
   // 循环阻塞
   for {
      select {
      case val := <-ch1:
         fmt.Println("ch1:", val)
      case val := <-ch2:
         fmt.Println("ch2:", val)
      }
   }
}

5. 超时控制

func main() {
   err := ConnectServer(time.Second * 3)
   fmt.Println(err)

   // 尽量避免非幂等端超时操作
   err = OrderHandler(time.Second * 3)
   fmt.Println(err)

   var quit string
   fmt.Scanln(&quit)
}

// 幂等案例 :超时后并没有终止工作的子协程
func ConnectServer(timeout time.Duration) (err error) {
   var done = make(chan bool)
   // 异步连接
   go func() {
      fmt.Println("连接到远程服务器中...")
      time.Sleep(time.Second * 5)
      done <- true
   }()

   // 阻塞:等待ok或超时信号
   select {
   case <-time.After(timeout):
      err = errors.New("连接超时...")
   case <-done:
      err = nil
   }
   return
}

// 非幂等案例:订单处理,虽然客户端收到订单超时提示,但是服务端还是完成了流程操作
func OrderHandler(timeout time.Duration) (err error) {
   var done = make(chan bool)
   go func() {
      time.Sleep(time.Second * 5)
      fmt.Println("订单处理完成")
      done <- true
   }()

   select {
   case <-time.After(timeout):
      err = errors.New("订单处理超时...")
   case <-done:
      err = nil
   }
   return
}

6. 并发控制

//https://www.jianshu.com/p/42e89de33065
func Run(task_id, sleeptime, timeout int, ch chan string) {
   ch_run := make(chan string)
   go run(task_id, sleeptime, ch_run)
   select {
   case re := <-ch_run:
      ch <- re
   case <-time.After(time.Duration(timeout) * time.Second):
      re := fmt.Sprintf("task id %d , timeout", task_id)
      ch <- re
   }
}

func run(task_id, sleeptime int, ch chan string) {
   time.Sleep(time.Duration(sleeptime) * time.Second)
   ch <- fmt.Sprintf("task id %d , sleep %d second", task_id, sleeptime)
   return
}

func main() {
   input := []int{3, 2, 1}
   timeout := 2
   chLimit := make(chan bool, 1)
   chs := make([]chan string, len(input))
   limitFunc := func(chLimit chan bool, ch chan string, task_id, sleeptime, timeout int) {
      Run(task_id, sleeptime, timeout, ch)
      <-chLimit
   }
   startTime := time.Now()
   fmt.Println("Multirun start")
   for i, sleeptime := range input {
      chs[i] = make(chan string, 1)
      chLimit <- true
      go limitFunc(chLimit, chs[i], i, sleeptime, timeout)
   }

   for _, ch := range chs {
      fmt.Println(<-ch)
   }
   endTime := time.Now()
   fmt.Printf("Multissh finished. Process time %s. Number of task is %d", endTime.Sub(startTime), len(input))
}

7. 时间通道

func main() {
   //定时器(一次计时):sleep、after、timer(推荐)
   After()
   Timer()

   // 计时器(周期计时)
   Ticker()
}

// 定时器
func After() {
   fmt.Println("三秒倒计时开始 :")
   at := <-time.After(time.Second * 3)
   fmt.Printf("[%s]引爆炸弹~~~ 💣 ~~~", at.Format("20060102150405"))
}

// 定时器 (Timer是可控的一种定时器)
func Timer() {
   fmt.Println("三秒后引爆炸弹:")
   stoped := false
   timer := time.NewTimer(time.Second * 1)
   timer.Reset(3 * time.Second) //重置(修改)为3秒
   //stoped = timer.Stop() //拆除定时器

   if !stoped {
      <-timer.C
      fmt.Println("炸弹💣引爆了~~~")
   } else {
      fmt.Println("炸弹拆除了~~~")
   }
}

// 计时器(周期计时)
func Ticker() {
   // 方式二: 返回时间(单)通道 <-chan Time
   tickers := time.Tick(1 * time.Second)
   for t := range tickers {
      fmt.Println("time beat: ", t.Second())
   }
}

8. 优雅退出

// https://blog.csdn.net/skh2015java/article/details/99468586

9. Feature模式

//https://blog.csdn.net/weixin_33733810/article/details/89063283
//https://blog.csdn.net/htyu_0203_39/article/details/51523517
func Future(f func() (interface{}, error)) func() (interface{}, error) {
   var result interface{}
   var err error

   c := make(chan struct{}, 1)
   go func() {
      defer close(c)
      result, err = f()
   }()

   return func() (interface{}, error) {
      <-c
      return result, err
   }
}

func main() {
   url := "http://labs.strava.com"
   future := Future(func() (interface{}, error) {
      resp, err := http.Get(url)
      if err != nil {
         return nil, err
      }
      defer resp.Body.Close()
      return ioutil.ReadAll(resp.Body)
   })

   // do many other things
   b, err := future()
   body, _ := b.([]byte)

   log.Printf("response length: %d", len(body))
   log.Printf("request error: %v", err)
}

10. 斐波那契

func fibonacci() func() int {
	x, y := 1, 0
	return func() int {
		x, y = y, x+y
		return x
	}
}

func main() {
	f := fibonacci()
	for i := 0; i < 10; i++ {
		fmt.Println(f())
	}
}
func fibonacci2(n int, c chan int) {
	x, y := 1, 0
	for i := 0; i < n; i++ {
		x, y = y, x+y
		c <- x
	}
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci2(cap(c), c)
	for v := range c {
		fmt.Println(v)
	}
}
func fibonacci3(c, quit chan int) {
	x, y := 0, 1
	for {
		select {
		case c <- x:
			x, y = y, x+y
		case <-quit:
			fmt.Println("quit")
			return
		}
	}
}

func main() {
	c := make(chan int)
	quit := make(chan int)
	go func() {
		for i := 0; i < 10; i++ {
			fmt.Println(<-c)
		}
		quit <- 0
	}()

	fibonacci3(c, quit)
}

package main

import "fmt"

// fibonacci 函数会返回一个返回 int 的函数。
func fibonacci() func() int {
    x1, x2 := 0, 1
    sum := 0
    return func() int {
        sum = x1 + x2
        x1 = x2
        x2 = sum
        return sum
    }
}

func main() {
    f := fibonacci()
    for i := 0; i < 10; i++ {
        fmt.Print(f(),", ")
    }
    fmt.Println("...")
}



参考:
https://blog.csdn.net/xieyun1977/article/details/73495885
https://studygolang.com/articles/2423
https://blog.csdn.net/eventer123/article/details/97777695
https://blog.csdn.net/weixin_33883178/article/details/86246652
https://www.cnblogs.com/piperck/p/6480198.html
https://blog.csdn.net/shenshouer/article/details/53401553
https://www.cnblogs.com/tobycnblogs/p/9935465.html
https://blog.csdn.net/zg_hover/article/details/81453379

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值