英文:
Is `make(chan _, _)` atomic?
问题
修改一个消费者正在读取的通道是否是线程安全的?
考虑以下代码:
func main(){
channel := make(chan int, 3)
channel_ptr := &channel
go supplier (channel_ptr)
go consumer (channel_ptr)
temp = *channel_ptr
// 重要部分
*channel_ptr = make(chan int, 5)
more := true
for more{
select {
case msg := <-temp:
*channel_ptr <- msg
default:
more = false
}
}
// 无限期阻塞主线程以保持子线程活动
<-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
按照我想要的方式工作,我应该得到以下属性:
- 程序总是输出 0 1 2 3 4
- 程序不会因为尝试从未初始化的通道读取而导致恐慌(即,我标记为
重要部分
的部分是原子的)
从几次测试运行来看,这似乎是正确的,但我在文档中找不到相关信息,我担心会出现微妙的竞态条件。
更新
是的,我之前的做法是行不通的。这个线程可能已经被埋没了,但有人知道如何动态调整缓冲通道的大小吗?
英文:
Is it thread-safe to modify the channel that a consumer is reading from?
Consider the following code:
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
}
}
If channels and make
work the way that I want them to, I should get the following properties:
- The program always outputs 0 1 2 3 4
- 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:
$ run -race t.go
==================
警告:数据竞争
主goroutine写入地址0x00c420086018:
main.main()
/Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128
其他goroutine读取地址0x00c420086018:
main.supplier()
/Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51
Goroutine 6 (正在运行) 创建于:
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
==================
警告:数据竞争
其他goroutine读取地址0x00c420086018:
main.supplier()
/Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:37 +0x51
主goroutine写入地址0x00c420086018:
main.main()
/Users/kjk/src/go/src/github.com/kjk/go-cookbook/start-mysql-in-docker-go/t.go:14 +0x128
Goroutine 6 (正在运行) 创建于:
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的值,你也会遇到相同的问题。
英文:
It's not thread safe.
If you run with -race
flag to use race detector, you'll see the bug:
$ 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
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论