有一说一,这篇文章有点题目党了,然而相对是干货。
曾经有很多对于 channel 的文章,为什么我还要写呢?任何知识点,只有你想,就能够从不同的角度切入!那就写点 channel 利用相干的货色。通过不同场景应用 channel 个性加深了解!所以在看这篇文章之前,首先得先去理解 channel。
由 channel 引发的血案
下面那篇文章漏了一个我感觉很要害的知识点,并且咱们还常常在下面犯错误。即便是那些牛逼的开源我的项目,也有过相似 bug。
我的问题是:channel 的哪些操作会引发 panic?
1.敞开一个 nil 值 channel 会引发 panic。
package mainfunc main() { var ch chan struct{} close(ch)}
2.敞开一个已敞开的 channel 会引发 panic。
package mainfunc main() { ch := make(chan struct{}) close(ch) close(ch)}
3.向一个已敞开的 channel 发送数据。
package mainfunc main() { ch := make(chan struct{}) close(ch) ch <- struct{}{}}
以上三种 channel 操作会引发 panic。
你可能会说,我咋么会犯这么愚昧的谬误。这只是一个很简略的例子,理论我的项目是很简单的,一不小心,你就会忘了本人曾在哪一个 g 里敞开过 channel。
如果你对某块代码没有安全感,置信我,就算它中午不出事,早晚也得出事。
channel 的一些利用
- 信号告诉
- 超时管制
- 生产生产模型
- 数据传递
- 管制并发数
- 互斥锁
- one million……
1.信号告诉
常常会有这样的场景,当信息收集实现,告诉上游开始计算数据。
package mainimport ( "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 mainimport ("fmt""unsafe")func main() { res := struct{}{} fmt.Println("占用空间:", unsafe.Sizeof(res))}//占用空间: 0
2.执行工作超时
咱们在做工作解决的时候,并不能保障工作的解决工夫,通常会加上一些超时管制做异样的解决。
package mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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 mainimport ( "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
如果文章对你有所帮忙,点赞、转发、留言都是一种反对!
欢送关注公众号 吴亲强的深夜食堂,一起学习。