关于golang:channel-实战应用这篇就够了

53次阅读

共计 3966 个字符,预计需要花费 10 分钟才能阅读完成。

有一说一,这篇文章有点题目党了,然而相对是干货。

曾经有很多对于 channel 的文章,为什么我还要写呢?任何知识点,只有你想,就能够从不同的角度切入!那就写点 channel 利用相干的货色。通过不同场景应用 channel 个性加深了解!所以在看这篇文章之前,首先得先去理解 channel。

由 channel 引发的血案

下面那篇文章漏了一个我感觉很要害的知识点,并且咱们还常常在下面犯错误。即便是那些牛逼的开源我的项目,也有过相似 bug。

我的问题是:channel 的哪些操作会引发 panic?

1. 敞开一个 nil 值 channel 会引发 panic。
package main

func main() {var ch chan struct{}
  close(ch)
}

2. 敞开一个已敞开的 channel 会引发 panic。

package main

func main() {ch := make(chan struct{})
  close(ch)
  close(ch)
}

3. 向一个已敞开的 channel 发送数据。

package main

func main() {ch := make(chan struct{})
  close(ch)
  ch <- struct{}{}
}

以上三种 channel 操作会引发 panic。

你可能会说,我咋么会犯这么愚昧的谬误。这只是一个很简略的例子,理论我的项目是很简单的,一不小心,你就会忘了本人曾在哪一个 g 里敞开过 channel。

如果你对某块代码没有安全感,置信我,就算它中午不出事,早晚也得出事。

channel 的一些利用

  • 信号告诉
  • 超时管制
  • 生产生产模型
  • 数据传递
  • 管制并发数
  • 互斥锁
  • one million……

1. 信号告诉

常常会有这样的场景,当信息收集实现,告诉上游开始计算数据。

package main

import (
  "fmt"
  "time"
)

func main() {isOver := make(chan struct{})
  go func() {collectMsg(isOver)
  }()
  <-isOver
  calculateMsg()}

// 采集
func collectMsg(isOver chan struct{}) {time.Sleep(500 * time.Millisecond)
  fmt.Println("实现采集工具")
  isOver <- struct{}{}
}

// 计算
func calculateMsg() {fmt.Println("开始进行数据分析")
}

如果只是单纯的应用告诉操作,那么类型就应用 struct{}。因为空构造体在 go 中是不占用内存空间的,不信你看。

package main

import (
"fmt"
"unsafe"
)

func main() {res := struct{}{}
  fmt.Println("占用空间:", unsafe.Sizeof(res))
}
// 占用空间: 0

2. 执行工作超时

咱们在做工作解决的时候,并不能保障工作的解决工夫,通常会加上一些超时管制做异样的解决。

package main

import (
  "fmt"
  "time"
)

func main() {
  select {case <-doWork():
    fmt.Println("工作完结")
  case <-time.After(1 * time.Second):
    fmt.Println("工作解决超时")
  }
}

func doWork() <-chan struct{} {ch := make(chan struct{})
  go func() {
    // 工作解决耗时
    time.Sleep(2 * time.Second)
  }()
  return ch
}

3. 生产生产模型

生产者只须要关注生产,而不必去理睬消费者的消费行为,更不必关怀消费者是否执行结束。而消费者只关怀生产工作,而不须要关注如何生产。

package main

import (
  "fmt"
  "time"
)

func main() {ch := make(chan int, 10)
  go consumer(ch)
  go producer(ch)
  time.Sleep(3 * time.Second)
}

// 一个生产者
func producer(ch chan int) {
  for i := 0; i < 10; i++ {ch <- i}
  close(ch)
}

// 消费者
func consumer(task <-chan int) {
  for i := 0; i < 5; i++ {
    // 5 个消费者
    go func(id int) {
      for {
        item, ok := <-task
        // 如果等于 false 阐明通道已敞开
        if !ok {return}
        fmt.Printf("消费者:%d,生产了:%dn", id, item)
        // 给他人一点机会不会吃亏
        time.Sleep(50 * time.Millisecond)
      }
    }(i)
  }
}

4. 数据传递

极客上一道有意思的题,假如有 4 个 goroutine,编号为 1,2,3,4。每秒钟会有一个 goroutine 打印出它本人的编号。当初让你写一个程序,要求输入的编号总是依照 1,2,3,4 这样的程序打印。相似下图,

package main

import (
  "fmt"
  "time"
)

type token struct{}

func main() {
  num := 4
  var chs []chan token
  // 4 个 work
  for i := 0; i < num; i++ {chs = append(chs, make(chan token))
  }
  for j := 0; j < num; j++ {go worker(j, chs[j], chs[(j+1)%num])
  }
  // 先把令牌交给第一个
  chs[0] <- struct{}{}
  select {}}

func worker(id int, ch chan token, next chan token) {
  for {
    // 对应 work 获得令牌
    token := <-ch
    fmt.Println(id + 1)
    time.Sleep(1 * time.Second)
    // 传递给下一个
    next <- token
  }
}

5. 管制并发数

我常常会写一些脚本,在凌晨的时候对内或者对外拉取数据,然而如果不对并发申请加以控制,往往会导致 groutine 泛滥,进而打满 CPU 资源。往往不能管制的货色意味着不好的事件将要产生。对于咱们来说,能够通过 channel 来管制并发数。

package main

import (
  "fmt"
  "time"
)

func main() {limit := make(chan struct{}, 10)
  jobCount := 100
  for i := 0; i < jobCount; i++ {go func(index int) {limit <- struct{}{}
      job(index)
      <-limit
    }(i)
  }
  time.Sleep(20 * time.Second)
}

func job(index int) {
  // 耗时工作
  time.Sleep(1 * time.Second)
  fmt.Printf("工作:%d 已实现 n", index)
}

当然了,sync.waitGroup 也能够。

package main

import (
  "fmt"
  "sync"
  "time"
)

func main() {
  var wg sync.WaitGroup
  jobCount := 100
  limit := 10
  for i := 0; i <= jobCount; i += limit {
    for j := 0; j < i; j++ {wg.Add(1)
      go func(item int) {defer wg.Done()
        job(item)
      }(j)
    }
    wg.Wait()}
}

func job(index int) {
  // 耗时工作
  time.Sleep(1 * time.Second)
  fmt.Printf("工作:%d 已实现 n", index)
}

6. 互斥锁

咱们也能够通过 channel 实现一个小小的互斥锁。通过设置一个缓冲区为 1 的通道,如果胜利地往通道发送数据,阐明拿到锁,否则锁被他人拿了,期待别人解锁。

package main

import (
  "fmt"
  "time"
)

type ticket struct{}

type Mutex struct {ch chan ticket}

// 创立一个缓冲区为 1 的通道作
func newMutex() *Mutex {return &Mutex{ch: make(chan ticket, 1)}
}

// 谁能往缓冲区为 1 的通道放入数据,谁就获取了锁
func (m *Mutex) Lock() {m.ch <- struct{}{}}

// 解锁就把数据取出
func (m *Mutex) unLock() {
  select {
  case <-m.ch:
  default:
    panic("曾经解锁了")
  }
}

func main() {mutex := newMutex()
  go func() {
    // 如果是 1 先拿到锁,那么 2 就要等 1 秒能力拿到锁
    mutex.Lock()
    fmt.Println("工作 1 拿到锁了")
    time.Sleep(1 * time.Second)
    mutex.unLock()}()
  go func() {mutex.Lock()
    // 如果是 2 拿先到锁,那么 1 就要等 2 秒能力拿到锁
    fmt.Println("工作 2 拿到锁了")
    time.Sleep(2 * time.Second)
    mutex.unLock()}()
  time.Sleep(500 * time.Millisecond)
  // 用了一点小伎俩这里最初能力拿到锁
  mutex.Lock()
  mutex.unLock()
  close(mutex.ch)
}

到这里,这篇文章曾经序幕了。当然我只是列举了局部 channel 的利用场景。你齐全能够施展本人的设想,在理论工作中,构建更完满且贴近生产的设计。

如果你还有其余不同的利用模式场景,欢送下方留言和我交换。

另外源码我放在 github 上了,地址:https://github.com/wuqinqiang/Go_Concurrency

如果文章对你有所帮忙,点赞、转发、留言都是一种反对!
欢送关注公众号 吴亲强的深夜食堂,一起学习。

正文完
 0