`make(chan _, _)` 是原子操作吗?

huangapple go评论114阅读模式
英文:

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

问题

修改一个消费者正在读取的通道是否是线程安全的?

考虑以下代码:

  1. func main(){
  2. channel := make(chan int, 3)
  3. channel_ptr := &channel
  4. go supplier (channel_ptr)
  5. go consumer (channel_ptr)
  6. temp = *channel_ptr
  7. // 重要部分
  8. *channel_ptr = make(chan int, 5)
  9. more := true
  10. for more{
  11. select {
  12. case msg := <-temp:
  13. *channel_ptr <- msg
  14. default:
  15. more = false
  16. }
  17. }
  18. // 无限期阻塞主线程以保持子线程活动
  19. <-make(chan bool)
  20. }
  21. func consumer(c *chan int){
  22. for true{
  23. fmt.Println(<-(*c))
  24. }
  25. }
  26. func supplier(c *chan int){
  27. for i :=0; i < 5; i ++{
  28. (*c)<-i
  29. }
  30. }

如果通道和make按照我想要的方式工作,我应该得到以下属性:

  1. 程序总是输出 0 1 2 3 4
  2. 程序不会因为尝试从未初始化的通道读取而导致恐慌(即,我标记为重要部分的部分是原子的)

从几次测试运行来看,这似乎是正确的,但我在文档中找不到相关信息,我担心会出现微妙的竞态条件。

更新

是的,我之前的做法是行不通的。这个线程可能已经被埋没了,但有人知道如何动态调整缓冲通道的大小吗?

英文:

Is it thread-safe to modify the channel that a consumer is reading from?

Consider the following code:

  1. func main(){
  2. channel := make(chan int, 3)
  3. channel_ptr := &amp;channel
  4. go supplier (channel_ptr)
  5. go consumer (channel_ptr)
  6. temp = *channel_ptr
  7. // Important bit
  8. *channel_ptr = make(chan int, 5)
  9. more := true
  10. for more{
  11. select {
  12. case msg := &lt;-temp:
  13. *channel_ptr &lt;- msg
  14. default:
  15. more = false
  16. }
  17. }
  18. // Block main indefinitely to keep the children alive
  19. &lt;-make(chan bool)
  20. }
  21. func consumer(c *chan int){
  22. for true{
  23. fmt.Println(&lt;-(*c))
  24. }
  25. }
  26. func supplier(c *chan int){
  27. for i :=0; i &lt; 5; i ++{
  28. (*c)&lt;-i
  29. }
  30. }

If channels and make work the way that I want them to, I should get the following properties:

  1. The program always outputs 0 1 2 3 4
  2. The program will never panic from trying to read from a non-initialized channel (IE, the part I labelled Important bit is atomic)

From several test runs, this seems to be true, but I can't find it anywhere in the documentation and I'm worried about subtle race conditions.

Update

Yeah, what I was doing doesn't work. This thread is probably buried at this point, but does anybody know how to dynamically resize a buffered channel?

答案1

得分: 1

这段代码存在线程安全问题。

如果你使用-race标志来运行,以使用竞态检测器,你会看到这个bug:

  1. $ run -race t.go
  2. ==================
  3. 警告:数据竞争
  4. goroutine写入地址0x00c420086018
  5. main.main()
  6. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128
  7. 其他goroutine读取地址0x00c420086018
  8. main.supplier()
  9. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51
  10. Goroutine 6 (正在运行) 创建于:
  11. main.main()
  12. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:9 +0xb4
  13. 0
  14. ==================
  15. 1
  16. 2
  17. 3
  18. ==================
  19. 警告:数据竞争
  20. 其他goroutine读取地址0x00c420086018
  21. main.supplier()
  22. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51
  23. goroutine写入地址0x00c420086018
  24. main.main()
  25. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128
  26. Goroutine 6 (正在运行) 创建于:
  27. main.main()
  28. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:9 +0xb4
  29. ==================
  30. 4

作为一个经验法则,你不应该将通道作为指针传递。通道在内部已经是一个指针。

稍微退后一点:我不明白你想要实现什么。

我猜你有一个原因要将通道作为指针传递。在Go中使用通道的模式是:你创建它一次,然后将它作为值传递。你不传递它的指针,也不在创建后修改它。

在你的示例中,问题在于你有一个共享的内存片段(由channel_ptr指向的内存地址),你在一个线程中写入该内存,而另一个线程读取它。这就是数据竞争。

这不仅仅适用于通道,如果它是指向int的指针,并且两个线程正在修改int的值,你也会遇到相同的问题。

英文:

It's not thread safe.

If you run with -race flag to use race detector, you'll see the bug:

  1. $ run -race t.go
  2. ==================
  3. WARNING: DATA RACE
  4. Write at 0x00c420086018 by main goroutine:
  5. main.main()
  6. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128
  7. Previous read at 0x00c420086018 by goroutine 6:
  8. main.supplier()
  9. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51
  10. Goroutine 6 (running) created at:
  11. main.main()
  12. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:9 +0xb4
  13. 0
  14. ==================
  15. 1
  16. 2
  17. 3
  18. ==================
  19. WARNING: DATA RACE
  20. Read at 0x00c420086018 by goroutine 6:
  21. main.supplier()
  22. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51
  23. Previous write at 0x00c420086018 by main goroutine:
  24. main.main()
  25. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128
  26. Goroutine 6 (running) created at:
  27. main.main()
  28. /Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:9 +0xb4
  29. ==================
  30. 4

As a rule of thumb, you should never pass channel as a pointer. Channel already is a pointer internally.

Stepping back a bit: I don't understand what you're trying to achieve.

I guess there's a reason you're trying to pass a channel as a pointer. The pattern of using channels in Go is: you create it once and you pass it around as value. You don't pass a pointer to it and you never modify it after creation.

In your example the problem is that you have a shared piece of memory (memory address pointed to by channel_ptr) and you write to that memory in one thread while some other thread reads it. That's data race.

It's not specific to a channel, you would have the same issue if it was pointer to an int and two threads were modifying the value of an int.

huangapple
  • 本文由 发表于 2017年7月7日 23:34:59
  • 转载请务必保留本文链接:https://go.coder-hub.com/44974701.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定