2016-06-20 19 views
4

Offensichtlich habe ich eine Race Condition in meinem Go-Code. Aber ich kann es nicht finden, da ich ziemlich sicher bin, dass ich richtig synchronisiere. Nach stundenlangem Debuggen können Sie mir wahrscheinlich helfen, es zu finden.Golang: Seltsames Verhalten mit Funktionstyp

Zunächst einmal ist hier meine (sehr vereinfacht) Code:

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 
} 

Die große Frage ist nun: Warum ist p.callback<nil> in p.parse während p.test ist nicht, obwohl diese beiden an der gleichen gesetzt Zeit?

Und das Zeug sollte mit dem Kanal p.callbackSet synchronisiert werden?!

Fully runnable Beispiel bei https://play.golang.org/p/14vn5Tie5Y

Ich habe versucht, durch einen einfacheren die Hauptfunktion zu ersetzen. Ich vermute, dass der Fehler irgendwo in der Dialog Struktur liegt. Wenn ich die Verwendung umgehe, kann ich das Problem nicht reproduzieren:

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 
} 

Der Rest des Codes bleibt gleich. Ein weiteres spielbares Beispiel der modifizierten (Arbeits-) Version hier: https://play.golang.org/p/0Y0nKbfcrv

+0

Meine Antwort ist (zur Zeit) falsch, aber ich habe durch den Spielplatz geschaut: das Problem ist nicht exklusiv für 'p.callback', als ob man versucht,' p.test' in 'SetCallback()' zu ändern der Standardwert später. –

Antwort

7

Dies ist, weil Sie das Parser Objekt von Wert sind, zu speichern und ein Dialog nach Wert von CreateDialog zurück.

Die ursprüngliche Parser Instanz, die in CreateDialog erstellt wurde, geht verloren, wenn die Dialog Instanz als Wert zurückgegeben wird.

Es ist die ursprüngliche Parser, die analysiert wird, und empfängt den Rückruf als protokolliert.

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) 
    }) 
} 

Deshalb ist es zu beheben Sie drei eines tun:

1) StartParsing in main Rufen.

func main() { 
    dialog, _ := CreateDialog() 
    dialog.Parser.StartParsing(); 
    dialog.OnMessage(func(m Message){ 
     log.Println("Message: ", m) 
    }) 
} 

2) Shop Parser als Zeiger in Dialog:

func NewParser() *Parser { 
    p := &Parser{} 
    p.test = 100 
    p.callbackSet = make(chan bool) 
    return p 
} 

type Dialog struct { 
    Parser *Parser 
} 

3) Zurück Dialog als Zeiger in von CreateDialog:

func CreateDialog() (*Dialog, error) { 
    d := &Dialog{} 
    d.Parser = NewParser() 
    d.Parser.StartParsing() 
    return d, nil 
} 

Das es beheben sollte.

+0

Von "Die ursprüngliche' Parser' -Instanz, die in 'CreateDialog' erstellt wurde, geht verloren, wenn die' Dialog' -Instanz durch Wert zurückgegeben wird. ", Meinst du, dass' Parser' im Dialogobjekt durch ein neues Parser-Objekt ersetzt wird ? – Atmocreations

+0

Der 'Parser' im Dialog und alle seine Eigenschaften werden nach Wert kopiert. Daher wird der "Test" -Wert von 100 kopiert, der "Callback" -Wert von Nil wird kopiert und der "CallbackSet" -Kanal (ein impliziter Referenztyp) wird kopiert. Sie können dies selbst sehen, indem Sie 'log.Println'-Anweisungen in' CreateDialog' und 'main' setzen. – kbirk

+0

Aww ... danke! Es wirkt jetzt wie ein Zauber! – Atmocreations