记一次golang中channel的错误使用

包牙齿 · · 1599 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

前段时间,在项目中遇到个问题,一直到测试时发现goroutine还在跑,总觉得不对,写了demo跑了发现,果然是有goroutine没有停掉,废话不多说,直接上代码

package main

import (
    "fmt"
    "time"
)

type Adapter struct {
    stop chan struct{}
}

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

    a := Adapter{stop: make(chan struct{})}
    go a.tick()
    time.Sleep(3 * time.Second)
    close(a.stop)
    a.stop = nil
    <-ch
}

func (a *Adapter) tick() {
    t := time.Tick(time.Second)
    index := 0
    for {
        select {
        case <-t:
            fmt.Println("index:", index)
            index++
        case <-a.stop:
            fmt.Println("tick stop")
            return
        }
    }
}

结果,是。。。

index: 0
index: 1
index: 2
index: 3
index: 4
index: 5
index: 6
index: 7
index: 8
index: 9
index: 10
index: 11
index: 12
index: 13
index: 14
index: 15
index: 16
index: 17
index: 18
index: 19
index: 20
...

tick stop没有打印?这个tick协程没有停掉,导致time.Tick这个channel一直在执行。

然后我们在close(a.stop)后插入了一行代码 time.Sleep(1time.Millisecond)*,奇迹发生了

package main

import (
    "fmt"
    "time"
)

type Adapter struct {
    stop chan struct{}
}

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

    a := Adapter{stop: make(chan struct{})}
    go a.tick()
    time.Sleep(3 * time.Second)
    close(a.stop)
    time.Sleep(1 * time.Millisecond)
    a.stop = nil
    <-ch
}

func (a *Adapter) tick() {
    t := time.Tick(time.Second)
    index := 0
    for {
        select {
        case <-t:
            fmt.Println("index:", index)
            index++
        case <-a.stop:
            fmt.Println("tick stop")
            return
        }
    }
}

index: 0
index: 1
index: 2
tick stop

上面这个样做视乎有些问题,如果通过close(a.stop)来捕获channel信号,如果这个方法被重复调用,导致会发生错误。

panic: close of closed channel
goroutine 1 [running]:

改进

package main

import (
    "fmt"
    "log"
    "time"
)

type Adapter struct {
    stop chan struct{}
}

func main() {
    ch := make(chan struct{})
    a := Adapter{stop: make(chan struct{})}
    go a.tick()
    time.Sleep(3 * time.Second)
    err := a.closeTick()
    if err!=nil{
        log.Fatal(err)
    }
    //test do again
    err = a.closeTick()
    if err!=nil{
        log.Fatal(err)
    }
    <-ch
}

func (a *Adapter) closeTick() error {
    fmt.Println("do closeTick")
    if a.stop == nil {
        return fmt.Errorf("a.stop is nil")
    }
    a.stop <- struct{}{}
    return nil
}

func (a *Adapter) tick() {
    t := time.Tick(time.Second)
    index := 0
    if a.stop == nil {
        fmt.Println("a.stop is nil")
        return
    }
    for {
        select {
        case <-t:
            fmt.Println("index:", index)
            index++
        case <-a.stop:
            fmt.Println("tick stop")
            close(a.stop)
            a.stop = nil
            return
        }
    }
}

这里没有利用在外部手动close channel,而是给channel传递匿名结构体,使stop接收信号,停掉tick协程


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:包牙齿

查看原文:记一次golang中channel的错误使用

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

1599 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传