Golang:函数类型的奇怪行为
Golang: Strange behaviour with function type
显然,我的 go 代码中存在竞争条件。但是我找不到它,因为我很确定可以正确同步。经过几个小时的调试,你可能可以帮我找到它。
首先,这是我的(非常简化的)代码:
package main
import (
"log"
"time"
)
type Parser struct {
callback Callback
callbackSet chan bool
test int
}
func NewParser() Parser {
p := Parser{}
p.test = 100
p.callbackSet = make(chan bool)
return p
}
func (p *Parser) SetCallback(newCallback Callback) {
log.Println("=> SET CALLBACK: ", newCallback)
p.test = 100
p.callback = newCallback
log.Println("=> SETTING CALLBACK DONE")
p.callbackSet <- true
}
func (p *Parser) StartParsing() {
go p.parse()
}
func (p *Parser) parse() {
cb := <-p.callbackSet
_ = cb
log.Println("Verify Callback: ", p.callback)
log.Println("Verify Test Variable: ", p.test)
funcDone := make(chan bool)
go func() {
time.Sleep(3 * time.Second) // Some io-Operation here
funcDone <- true
}()
_ = <-funcDone
}
type Callback func(Message)
type Message int
type Dialog struct {
Parser Parser
}
func CreateDialog() (Dialog, error) {
d := Dialog{}
d.Parser = NewParser()
d.Parser.StartParsing()
return d, nil
}
func (d *Dialog) OnMessage(callback Callback) {
log.Println("dialog.OnMessage: ", callback)
time.Sleep(3 * time.Second) // This sleep is just to prove the synchronization. It could be removed.
d.Parser.SetCallback(callback)
}
func main() {
dialog, _ := CreateDialog()
dialog.OnMessage(func(m Message){
log.Println("Message: ", m)
})
time.Sleep(5 * time.Second) // Not clean but just to await all of the output
}
现在最大的问题是:为什么 p.callback
<nil>
在 p.parse
而 p.test
不是,尽管这两个是同时设置的?
并且应该使用频道 p.callbackSet
同步内容?!
https://play.golang.org/p/14vn5Tie5Y
处的完全可运行示例
我尝试用更简单的函数替换主函数。我怀疑这个错误在 Dialog
结构中的某个地方。当我规避它的使用时,我无法重现问题:
func main() {
p := NewParser()
p.StartParsing()
p.SetCallback(func (m Message) {
log.Println("Message: ", m)
})
time.Sleep(5 * time.Second) // Not clean but just to await all of the output
}
其余代码保持不变。此处修改(工作)版本的另一个可玩示例:https://play.golang.org/p/0Y0nKbfcrv
这是因为您正在按值存储 Parser
对象并从 CreateDialog
按值返回 Dialog
。
当 Dialog
实例按值返回时,在 CreateDialog
中创建的原始 Parser
实例丢失。
正在解析的是原始Parser
,并接收记录的回调。
func CreateDialog() (Dialog, error) {
d := Dialog{}
d.Parser = NewParser()
d.Parser.StartParsing() // <-- this instance is parsing
return d, nil
}
func main() {
dialog, _ := CreateDialog()
// dialog.Parser <-- this is now a new instance which is NOT parsing
dialog.OnMessage(func(m Message){
log.Println("Message: ", m)
})
}
因此要修复它,您可以执行以下三种操作之一:
1) 在main
中调用StartParsing
。
func main() {
dialog, _ := CreateDialog()
dialog.Parser.StartParsing();
dialog.OnMessage(func(m Message){
log.Println("Message: ", m)
})
}
2) 将Parser
存储为对话框中的指针:
func NewParser() *Parser {
p := &Parser{}
p.test = 100
p.callbackSet = make(chan bool)
return p
}
type Dialog struct {
Parser *Parser
}
3) Return Dialog
作为来自 CreateDialog
:
的指针
func CreateDialog() (*Dialog, error) {
d := &Dialog{}
d.Parser = NewParser()
d.Parser.StartParsing()
return d, nil
}
这应该可以解决问题。
显然,我的 go 代码中存在竞争条件。但是我找不到它,因为我很确定可以正确同步。经过几个小时的调试,你可能可以帮我找到它。
首先,这是我的(非常简化的)代码:
package main
import (
"log"
"time"
)
type Parser struct {
callback Callback
callbackSet chan bool
test int
}
func NewParser() Parser {
p := Parser{}
p.test = 100
p.callbackSet = make(chan bool)
return p
}
func (p *Parser) SetCallback(newCallback Callback) {
log.Println("=> SET CALLBACK: ", newCallback)
p.test = 100
p.callback = newCallback
log.Println("=> SETTING CALLBACK DONE")
p.callbackSet <- true
}
func (p *Parser) StartParsing() {
go p.parse()
}
func (p *Parser) parse() {
cb := <-p.callbackSet
_ = cb
log.Println("Verify Callback: ", p.callback)
log.Println("Verify Test Variable: ", p.test)
funcDone := make(chan bool)
go func() {
time.Sleep(3 * time.Second) // Some io-Operation here
funcDone <- true
}()
_ = <-funcDone
}
type Callback func(Message)
type Message int
type Dialog struct {
Parser Parser
}
func CreateDialog() (Dialog, error) {
d := Dialog{}
d.Parser = NewParser()
d.Parser.StartParsing()
return d, nil
}
func (d *Dialog) OnMessage(callback Callback) {
log.Println("dialog.OnMessage: ", callback)
time.Sleep(3 * time.Second) // This sleep is just to prove the synchronization. It could be removed.
d.Parser.SetCallback(callback)
}
func main() {
dialog, _ := CreateDialog()
dialog.OnMessage(func(m Message){
log.Println("Message: ", m)
})
time.Sleep(5 * time.Second) // Not clean but just to await all of the output
}
现在最大的问题是:为什么 p.callback
<nil>
在 p.parse
而 p.test
不是,尽管这两个是同时设置的?
并且应该使用频道 p.callbackSet
同步内容?!
https://play.golang.org/p/14vn5Tie5Y
处的完全可运行示例我尝试用更简单的函数替换主函数。我怀疑这个错误在 Dialog
结构中的某个地方。当我规避它的使用时,我无法重现问题:
func main() {
p := NewParser()
p.StartParsing()
p.SetCallback(func (m Message) {
log.Println("Message: ", m)
})
time.Sleep(5 * time.Second) // Not clean but just to await all of the output
}
其余代码保持不变。此处修改(工作)版本的另一个可玩示例:https://play.golang.org/p/0Y0nKbfcrv
这是因为您正在按值存储 Parser
对象并从 CreateDialog
按值返回 Dialog
。
当 Dialog
实例按值返回时,在 CreateDialog
中创建的原始 Parser
实例丢失。
正在解析的是原始Parser
,并接收记录的回调。
func CreateDialog() (Dialog, error) {
d := Dialog{}
d.Parser = NewParser()
d.Parser.StartParsing() // <-- this instance is parsing
return d, nil
}
func main() {
dialog, _ := CreateDialog()
// dialog.Parser <-- this is now a new instance which is NOT parsing
dialog.OnMessage(func(m Message){
log.Println("Message: ", m)
})
}
因此要修复它,您可以执行以下三种操作之一:
1) 在main
中调用StartParsing
。
func main() {
dialog, _ := CreateDialog()
dialog.Parser.StartParsing();
dialog.OnMessage(func(m Message){
log.Println("Message: ", m)
})
}
2) 将Parser
存储为对话框中的指针:
func NewParser() *Parser {
p := &Parser{}
p.test = 100
p.callbackSet = make(chan bool)
return p
}
type Dialog struct {
Parser *Parser
}
3) Return Dialog
作为来自 CreateDialog
:
func CreateDialog() (*Dialog, error) {
d := &Dialog{}
d.Parser = NewParser()
d.Parser.StartParsing()
return d, nil
}
这应该可以解决问题。