是`make(chan _,_)`atomic?

时间:2017-07-07 15:35:00

标签: go concurrency race-condition goroutine

修改消费者正在阅读的频道是否可以线程安全?

请考虑以下代码:

func main(){
    channel     := make(chan int, 3)
    channel_ptr := &channel

    go supplier (channel_ptr)
    go consumer (channel_ptr)

    temp = *channel_ptr
    // Important bit
    *channel_ptr = make(chan int, 5)

    more := true
    for more{
        select {
            case msg := <-temp:
                *channel_ptr <- msg
            default:
                more = false
        }
    }
    // Block main indefinitely to keep the children alive
    <-make(chan bool)
}

func consumer(c *chan int){
    for true{
        fmt.Println(<-(*c))
    }
}

func supplier(c *chan int){
    for i :=0; i < 5; i ++{
        (*c)<-i
    }
}

如果频道和make按我希望的方式工作,我应该获得以下属性:

  1. 程序始终输出0 1 2 3 4
  2. 程序永远不会因尝试从非初始化通道读取而感到恐慌(IE,标记为Important bit的部分是原子的)
  3. 从几次测试开始,这似乎都是正确的,但我无法在文档中找到它,我担心细微的竞争条件。

    更新

    是的,我在做什么都行不通。此线程可能在此时被隐藏,但有人知道如何动态调整缓冲通道的大小吗?

1 个答案:

答案 0 :(得分:1)

它不是线程安全的。

如果您使用-race标记运行race detector,则会看到错误:

$ run -race t.go
==================
WARNING: DATA RACE
Write at 0x00c420086018 by main goroutine:
  main.main()
      /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128

Previous read at 0x00c420086018 by goroutine 6:
  main.supplier()
      /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51

Goroutine 6 (running) created at:
  main.main()
      /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:9 +0xb4
0
==================
1
2
3
==================
WARNING: DATA RACE
Read at 0x00c420086018 by goroutine 6:
  main.supplier()
      /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51

Previous write at 0x00c420086018 by main goroutine:
  main.main()
      /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128

Goroutine 6 (running) created at:
  main.main()
      /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:9 +0xb4
==================
4

根据经验,您不应该将频道作为指针传递。频道已经是内部指针。

退后一步:我不明白你想要实现的目标。

我猜你有一个原因是你试图将频道作为指针传递。在Go中使用频道的模式是:您创建一次并将其作为值传递。你没有传递指针,你永远不会在创建后修改它。

在你的例子中,问题是你有一块共享的内存(channel_ptr指向的内存地址),你在一个线程中写入该内存,而另一个线程读取它。那次数据竞赛。

它不是特定于某个频道,如果它是指向int的指针而两个线程正在修改int的值,则会遇到同样的问题。