不能写在一个goroutine并从另一个不确定地读取

时间:2018-04-02 19:55:07

标签: go goroutine

我正在学习go,我遇到了goroutines的问题。这是我的代码

package main

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

var counter = 0
var wg = sync.WaitGroup{}

func main() {
    ticker := time.NewTicker(time.Second)
    go func() {
        for range ticker.C {
            // wg.Add(1)
            // defer wg.Done()
            counter++
            fmt.Println(counter)
            //wg.Done()
        }
    }()

    ticker2 := time.NewTicker(time.Second * 2)
    wg.Add(1)
    go func() {
        for range ticker2.C {
            //defer wg.Done()
            fmt.Println(counter)
        }
    }()
    wg.Wait()
}

基本上,我想:

  • 一个名为counter
  • 的全局变量
  • 一个goroutine,每1秒钟不断更新此计数器
  • 另一个goroutine,每两秒钟一次打印此计数器

游乐场是here 我尝试使用WaitGroup,但我没有设法使用它。 使用此级别的代码,我有以下警告:

WARNING: DATA RACE
Read at 0x0000011d8318 by goroutine 8:
  runtime.convT2E64()

另一个问题是这个线程安全吗?我的意思是,我可以安全地在两个groroutines之外的main方法中使用counter吗?

1 个答案:

答案 0 :(得分:0)

一种方法可能是在打印时向第二次gorutine发送消息。

package main

import (
    "fmt"
    "time"
)

func main() {

    fmt.Println("Start");
    counter := 0

    printChannel := make( chan int)
    done := make(chan struct{} )

    go func(done <-chan struct{}, printChannel chan<- int  ){
        timer := time.NewTicker(time.Second)
        bDone := false;
        for !bDone {
            select {
            case <-timer.C:
                counter++
                if counter%2 == 0 {
                    printChannel <- counter
                }
            case <-done:
                bDone=true
            }
        }
    }(done, printChannel)

    go func(done <-chan struct{}, printChannel <-chan int ){
        bDone:=false
        for !bDone{
            select {
            case n := <-printChannel:
                fmt.Print(n, " ");
            case <-done:
                bDone=true
            }
        }
    }(done,printChannel)


    //whatever logic to stop
    go func() {
        <- time.After(21*time.Second)
        done <- struct{}{}
    }()

    <-done

    fmt.Println("\nEnd");

}

请注意,这里只有第3个gorutine来完成这个例子

输出:

Start
2 4 6 8 10 12 14 16 18 20 
End

为了改善它,您可以在没有bDone变量的情况下进行改进,您可以停止Ticker并在gorutine存在时添加适当的消息。 或者你可能想测试一下&#39; break&#39;带标签退出循环。
当您使用关闭(完成)频道而不是向其发送消息时,您可以测试效果 此外,如果您准备放松读/写顺序,您可以删除printChannel并使第二个gorutine使用另一个每2秒ping一次的自动收报机。