Golang的fmt.Println()导致游戏崩溃。

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

Golang fmt.Println() causes game crash

问题

我有一个使用Go语言编写的简单OpenGL程序。

当我编译并运行它时,在主游戏循环中大约进行了9次迭代后,程序崩溃并显示分段错误。

如果我在shouldRender函数中移除基于时间的逻辑,程序可以进行大约28-29次迭代后崩溃。

如果我在draw函数中移除对gl.Clear()的调用,程序可以进行到90次左右的迭代后崩溃。

如果我在shouldRender中移除对fmt.Println()的调用,游戏可以正常运行而不会崩溃。(我已经测试了将近2到3分钟,大约有1万帧)

这使我怀疑fmt.Println()的调用可能导致了分段错误。我是否误读了这些迹象?如果没有,为什么像Println()这样的核心函数会如此不稳定?

以下是你提供的代码:

package main

import (
    f "fmt"
    "github.com/go-gl/gl"
    glfw "github.com/go-gl/glfw3"
    "math"
    "time"
)

var (
    numRendered = 0
    lastDraw    = time.Now()
    fps         = 60
    seconds     = time.Now()
    attr        gl.AttribLocation
)

func main() {
    if !glfw.Init() {
        f.Println("Failed to init glfw")
        panic("Cannot initialize glfw library")
    }
    defer glfw.Terminate()

    //glfw.WindowHint(glfw.DepthBits, 16)
    window, err := glfw.CreateWindow(300, 300, "Wander", nil, nil)
    if err != nil {
        panic(err)
    }

    window.SetFramebufferSizeCallback(reshape)
    window.SetKeyCallback(key)
    window.MakeContextCurrent()
    glfw.SwapInterval(1)
    width, height := window.GetFramebufferSize()
    reshape(window, width, height)

    if gl.Init() != 0 {
        panic("Failed to init GL")
    }

    prog := setupProgram()
    defer prog.Delete()
    prog.Use()

    attr = prog.GetAttribLocation("offset")

    setup()
    for !window.ShouldClose() {
        if shouldRender() {
            draw()
        }
        animate()
        window.SwapBuffers()
        glfw.PollEvents()
    }
}

func setupProgram() (prog gl.Program) {
    vertexSource := `
        #version 430 core

        layout (location = 0) in vec4 offset;

        const vec4 vertecies[3] = vec4[3](
            vec4(0.25, 0.5, 0.5, 1.0),
            vec4(-0.25, 0.5, 0.5, 1.0),
            vec4(-0.25, -0.5, 0.5, 1.0)
        );

        void main(){
            gl_Position = vertecies[gl_VertexID] + offset;
        }`
    fragmentSource := `
        #version 430 core

        out vec4 color;

        void main(){
            color = vec4(1.0, 0.0, 0.0, 0.0); // red, blue, green, ??
        }`
    vert, frag := gl.CreateShader(gl.VERTEX_SHADER), gl.CreateShader(gl.FRAGMENT_SHADER)
    defer vert.Delete()
    defer frag.Delete()
    vert.Source(vertexSource)
    frag.Source(fragmentSource)
    vert.Compile()
    frag.Compile()

    prog = gl.CreateProgram()
    prog.AttachShader(vert)
    prog.AttachShader(frag)
    prog.Link()
    prog.Use()
    f.Println(prog.GetInfoLog())

    return
}

func key(window *glfw.Window, k glfw.Key, s int, action glfw.Action, mods glfw.ModifierKey) {
    if action != glfw.Press {
        return
    }

    switch glfw.Key(k) {
    case glfw.KeyEscape:
        window.SetShouldClose(true)
    default:
        return
    }
}

func reshape(window *glfw.Window, width, height int) {
    gl.Viewport(0, 0, width, height)
}
func draw() {
    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
    gl.DrawArrays(gl.TRIANGLES, 0, 3)
}
func shouldRender() bool {
    if int(time.Since(lastDraw)*time.Second) >= 1000/fps {
        //f.Println("rendering for the ", numRendered, " time")
        numRendered++
        lastDraw = time.Now()
        return true
    }

    return false
}

func animate() {
    now := float64(time.Since(seconds))

    offset := [4]float32{
        float32(math.Sin(now)),
        float32(math.Cos(now)),
        0.0, 0.0}
    attr.Attrib4fv(&offset)

    red := gl.GLclampf(math.Sin(now)*0.25 + 0.75)
    blue := gl.GLclampf(math.Cos(now)*0.25 + 0.75)
    green := gl.GLclampf(time.Since(seconds))
    _ = green

    gl.ClearColor(red, blue, 0.2, 0.0)
}

请注意,我只会翻译你提供的代码,不会回答关于翻译的问题。

英文:

I have this simple OpenGL program in Go.

When I compile and run it, the main game-loop gets through about 9 iterations before crashing with a segmentation violation.

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

If I remove the time-based logic in the shouldRender function, it makes it to about 28-29 iterations before crashing.

If I remove the call to gl.Clear() in the draw function, it lasts into the 90s before crashing.

If I remove the call to fmt.Println() in shouldRender, the game runs as expected without crashing. (I've tested it up to about 2 or 3 minutes, so almost 10 thousand frames)

This makes me suspect that the call to fmt.Println() is somehow responsible for the segmentation violation. Am I misreading the signs? If not, how is a core function like Println() so unstable?

package main
import (
f "fmt"
"github.com/go-gl/gl"
glfw "github.com/go-gl/glfw3"
"math"
"time"
)
var (
numRendered = 0
lastDraw = time.Now()
fps = 60
seconds = time.Now()
attr gl.AttribLocation
)
func main(){
if !glfw.Init(){
f.Println("Failed to init glfw")
panic("Cannot initialize glfw library")
}
defer glfw.Terminate()
//glfw.WindowHint(glfw.DepthBits, 16)
window, err := glfw.CreateWindow(300, 300, "Wander", nil, nil)
if err != nil{
panic(err)
}
window.SetFramebufferSizeCallback(reshape)
window.SetKeyCallback(key)
window.MakeContextCurrent()
glfw.SwapInterval(1)
width, height := window.GetFramebufferSize()
reshape(window, width, height)
if gl.Init() != 0 {
panic("Failed to init GL")
}
prog := setupProgram()
defer prog.Delete()
prog.Use()
attr = prog.GetAttribLocation("offset")
setup()
for !window.ShouldClose() {
if shouldRender(){
draw()
}
animate()
window.SwapBuffers()
glfw.PollEvents()
}
}
func setupProgram()(prog gl.Program){
vertexSource := `
#version 430 core
layout (location = 0) in vec4 offset;
const vec4 vertecies[3] = vec4[3](
vec4(0.25, 0.5, 0.5, 1.0),
vec4(-0.25, 0.5, 0.5, 1.0),
vec4(-0.25, -0.5, 0.5, 1.0)
);
void main(){
gl_Position = vertecies[gl_VertexID] + offset;
}`
fragmentSource := `
#version 430 core
out vec4 color;
void main(){
color = vec4(1.0, 0.0, 0.0, 0.0); // red, blue, green, ??
}`
vert, frag := gl.CreateShader(gl.VERTEX_SHADER), gl.CreateShader(gl.FRAGMENT_SHADER)
defer vert.Delete()
defer frag.Delete()
vert.Source(vertexSource)
frag.Source(fragmentSource)
vert.Compile()
frag.Compile()
prog = gl.CreateProgram()
prog.AttachShader(vert)
prog.AttachShader(frag)
prog.Link()
prog.Use()
f.Println(prog.GetInfoLog())
return
}
func key(window *glfw.Window, k glfw.Key, s int, action glfw.Action, mods glfw.ModifierKey) {
if action != glfw.Press {
return
}
switch glfw.Key(k){
case glfw.KeyEscape:
window.SetShouldClose(true);
default:
return
}
}
func reshape(window *glfw.Window, width, height int){
gl.Viewport(0, 0, width, height)
}
func draw(){
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.DrawArrays(gl.TRIANGLES, 0, 3)
}
func shouldRender() bool{
if int(time.Since(lastDraw) * time.Second) >= 1000/fps{
//f.Println("rendering for the ", numRendered, " time")
numRendered++
lastDraw = time.Now()
return true
}
return false;
}
func animate(){
now := float64(time.Since(seconds))
offset := [4]float32{
float32(math.Sin(now)),
float32(math.Cos(now)),
0.0,0.0}
attr.Attrib4fv(&offset)
red := gl.GLclampf(math.Sin(now) * 0.25 + 0.75)
blue := gl.GLclampf(math.Cos(now) * 0.25 + 0.75)
green := gl.GLclampf(time.Since(seconds))
_ = green;
gl.ClearColor(red, blue, 0.2, 0.0)
}

答案1

得分: 12

我在我的机器上运行了你的代码——64位Windows上的MinGW-w64。虽然我的带有println的代码运行时间比你的长得多(在崩溃之前超过30,000次调用),但我观察到了相同的行为。

堆栈跟踪报告了对gl函数的调用,这让我有所猜测:也许错误与OpenGL上下文有关。

确实,如果你添加以下代码:

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

和以下代码:

runtime.LockOSThread()

到main函数的顶部,错误就会消失(或者至少在我的机器上,通过锁定线程,它运行了几分钟,显然我不能证明它永远不会崩溃)。

当Goroutine在执行某些任务(如IO)时被阻塞时,Go运行时会偶尔分离出额外的线程来保持程序运行。

我怀疑发生的情况是,有时在调用Println时,Goroutine在系统调用上被阻塞,所以运行时通过在不同的线程上运行主Goroutine来“帮助”你。由于OpenGL上下文绑定到线程,这导致你的程序在GL调用上崩溃,因为你在错误的线程上调用它们。

在main函数的顶部添加runtime.LockOSThread()会强制主Goroutine始终在同一个线程上执行,从而保持所有的GL调用在正确的上下文中。

我应该补充说明,我简要地浏览了fmt包的源代码,从我所看到的来看,我不能证明Goroutine/线程的混乱绝对发生;然而,根据我对Go运行时的了解以及LockOSThread似乎可以修复问题的事实,我强烈怀疑这是问题的原因。

无论如何:当你使用依赖于将上下文绑定到单个线程的C库(如OpenGL或OpenAL)时,请确保始终使用runtime.LockOSThread将其运行的Goroutine锁定到一个线程上。

更新:我在Go调度器上找到了这份文档,如果我理解正确的话,它证实了我的怀疑。诸如打印之类的系统调用可以调用新线程来允许调用的Goroutine在程序在IO上被阻塞时继续执行。

英文:

I ran your code on my machine -- MinGW-w64 on 64-bit Windows. While my code with the println ran much longer than yours (over 30k calls before crashing) I observed the same behavior.

The stack trace reported it on calls to gl functions, this gave me an inkling: perhaps the error was related to the OpenGL context.

Indeed, if you add

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

And the line

runtime.LockOSThread()

To the top of the main function, the error goes away (or at least with the thread locked it ran several minutes on my machine, obviously I can't prove it will never crash).

When Goroutines are blocked doing certain tasks such as IO, the Go runtime will occasionally split off extra threads to keep the program moving.

I suspect that what it happening is that sometimes on a call to Println, the Goroutine becomes blocked on a syscall, so the runtime "helps" you by running your main goroutine on a different thread. Since OpenGL contexts are bound to a thread, this is causing your program to crash on GL calls because you're calling on the wrong thread.

Adding runtime.LockOSThread() to the top of main forces the main Goroutine to always execute on the same thread, and thus keeps all the GL calls in the correct context.

I should add that I briefly skimmed the source the fmt package and from what I saw I can't prove that the goroutine/thread nonsense is definitely happening; however, I strongly suspect that this is the case based on what I know about the Go runtime and the fact that LockOSThread seems to fix it.

Either way: when you're using a C library such as OpenGL or OpenAL that depends on having a context bound to a single thread, make sure to always lock the Goroutine it's running on to one thread with runtime.LockOSThread.

Update: I found this document on the Go scheduler, which, if I'm reading it correctly, confirms my suspicions. Syscalls such as printing can call new threads to be spawned to allow the calling goroutine to continue while the program is blocked on IO.

huangapple
  • 本文由 发表于 2014年1月9日 10:55:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/21010854.html
匿名

发表评论

匿名网友

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

确定