2014-01-09 9 views
5

Ich habe dieses einfache OpenGL-Programm in Go.Golang fmt.Println() verursacht Spielabsturz

Wenn ich es kompiliere und ausführe, durchläuft die Hauptspielschleife ungefähr 9 Iterationen, bevor sie mit einer Segmentierungsverletzung abstürzt.

rendering for the 0 time 
rendering for the 1 time 
rendering for the 2 time 
rendering for the 3 time 
rendering for the 4 time 
rendering for the 5 time 
rendering for the 6 time 
SIGSEGV: segmentation violation 
PC=0x7fdab95a0e29 
signal arrived during cgo execution 

runtime.cgocall(0x414f90, 0x7fdab9887e88) 
    /usr/lib/go/src/pkg/runtime/cgocall.c:149 +0x11b fp=0x7fdab9887e70 
github.com/go-gl/gl._Cfunc_glClear(0xc200004100) 
    github.com/go-gl/gl/_obj/_cgo_defun.c:340 +0x31 fp=0x7fdab9887e88 
github.com/go-gl/gl.Clear(0x4100) 
    /mnt/data/Dropbox/Coding/Go/src/github.com/go-gl/gl/gl.go:161 +0x25 fp=0x7fdab9887e98 
main.draw() 
    /home/josh/Coding/Go/src/github.com/JoshWillik/Wander/wander.go:120 +0x25 fp=0x7fdab9887eb8 
main.main() 
    /home/josh/Coding/Go/src/github.com/JoshWillik/Wander/wander.go:52 +0x300 fp=0x7fdab9887f48 
runtime.main() 
    /usr/lib/go/src/pkg/runtime/proc.c:220 +0x11f fp=0x7fdab9887fa0 
runtime.goexit() 
    /usr/lib/go/src/pkg/runtime/proc.c:1394 fp=0x7fdab9887fa8 

goroutine 3 [syscall]: 
runtime.goexit() 
    /usr/lib/go/src/pkg/runtime/proc.c:1394 

rax  0x0 
rbx  0x7fdab9887e88 
rcx  0x7fdab9887e88 
rdx  0x7fdab9887e20 
rdi  0x4100 
rsi  0xc210001900 
rbp  0xc21002a000 
rsp  0x7fdab2a4ddd8 
r8  0xc210001120 
r9  0x7fdab9887e20 
r10  0x0 
r11  0x286 
r12  0x0 
r13  0x7fdab9a74000 
r14  0x0 
r15  0x7fdab2a4e700 
rip  0x7fdab95a0e29 
rflags 0x10202 
cs  0x33 
fs  0x0 
gs  0x0 

Wenn ich die zeitbasierte Logik in der shouldRender Funktion zu entfernen, macht es zu etwa 28-29 Wiederholungen vor dem Absturz.

Wenn ich den Anruf zu gl.Clear() in der draw Funktion entfernen, dauert es in den 90er Jahren vor dem Absturz.

Wenn ich den Anruf zu fmt.Println() in shouldRender entfernen, wird das Spiel wie erwartet ausgeführt, ohne abzustürzen. (Ich habe es bis zu etwa 2 oder 3 Minuten getestet, also fast 10 000 Frames)

Das lässt mich vermuten, dass der Anruf an fmt.Println() irgendwie für die Segmentierungsverletzung verantwortlich ist. Verkenne ich die Zeichen falsch? Wenn nicht, wie ist eine Kernfunktion wie Println() so instabil?

+0

Was bedeutet der Code "_ = grün;" machen? – PaulQ

+0

Nur halbwilde Vermutung: versuch, runtime.LockOSThread() an den Anfang von main zu setzen und den Code mit Println auszuführen. Wenn es gut läuft, denke ich, dass ich die Antwort kenne. – LinearZoetrope

+0

@PaulQ, '_ = grün' lässt den Go-Compiler ignorieren, dass ich nie' grün' für irgendwas verwende – JoshWillik

Antwort

12

Ich habe Ihren Code auf meinem Computer - MinGW-W64 auf 64-Bit-Windows. Während mein Code mit dem println viel länger lief als du (über 30k Anrufe vor dem Absturz), beobachtete ich das gleiche Verhalten.

Die Stack-Trace meldete es bei Aufrufen von gl-Funktionen, dies gab mir eine Ahnung: Vielleicht war der Fehler auf den OpenGL-Kontext bezogen.

der Tat, wenn Sie

import (
//... 
"runtime" 
//... 
) 

hinzufügen und die Linie

runtime.LockOSThread() 

Nach oben von der Hauptfunktion, geht der Fehler weg (oder zumindest mit dem Gewinde verriegelt es mehrere Minuten lief an meiner Maschine kann ich natürlich nicht beweisen, dass es nie abstürzen wird).

Wenn Goroutines bestimmte Aufgaben wie IO blockieren, wird die Go-Laufzeit gelegentlich zusätzliche Threads abspalten, um das Programm in Bewegung zu halten.

Ich vermute, dass es passiert, dass manchmal bei einem Aufruf von Println die Goroutine auf einem Syscall blockiert wird, also "hilft" dir die Laufzeit, indem du deine Hauptgoroutine auf einem anderen Thread ausführst. Da OpenGL-Kontexte an einen Thread gebunden sind, stürzt Ihr Programm bei GL-Anrufen ab, weil Sie den falschen Thread aufrufen.

Das Hinzufügen von runtime.LockOSThread() an den Anfang von main erzwingt, dass die Haupt-Goroutine immer im selben Thread ausgeführt wird, und hält somit alle GL-Aufrufe im richtigen Kontext.

Ich sollte hinzufügen, dass ich kurz die Quelle das fmt-Paket überflogen und von dem, was ich gesehen habe, kann ich nicht beweisen, dass die Goroutine/Thread-Unsinn definitiv passiert; Ich vermute jedoch stark, dass dies der Fall ist, basierend auf dem, was ich über die Go-Laufzeit weiß, und der Tatsache, dass LockOSThread es zu reparieren scheint.

In beiden Fällen: Wenn Sie eine C-Bibliothek wie OpenGL oder OpenAL verwenden, die darauf angewiesen ist, dass ein Kontext an einen einzelnen Thread gebunden ist, müssen Sie die laufende Goroutine immer auf einem Thread mit runtime.LockOSThread sperren.

Update: Ich fand dieses Dokument auf the Go scheduler, die, wenn ich es richtig lese, meinen Verdacht bestätigt. Syscalls wie das Drucken können neue Threads aufrufen, die erzeugt werden sollen, damit die aufrufende Goroutine fortgesetzt werden kann, während das Programm bei IO blockiert ist.

+0

Das nimmt den Code von 9 Frames pro Absturz auf 30'000, ohne dass es zu einer Verlangsamung kommt. – JoshWillik