当没有通道准备好读取时,如何什么都不做?

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

How to do nothing when no channel is ready to be read?

问题

让我们以GoTour中的这个例子为例,因为它说明了我在只有事件时处理SDL事件的问题。

package main

import (
    "fmt"
    "time"
)

func main() {
    tick := time.Tick(1e8)
    boom := time.After(5e8)
    for {
        select {
        case <-tick:
            fmt.Println("tick.")
        case <-boom:
            fmt.Println("BOOM!")
            return
        default:
            fmt.Println("    .")
            time.Sleep(5e7)
        }
    }
}

这个代码是有效的。但是如果我不想在默认情况下打印或休眠,只想继续循环怎么办?我尝试了这样的代码:

    case <-boom:
        fmt.Println("BOOM!")
        return
    default: // 这里什么都没有。
    }
}

但是它会阻塞。

我在这里和那里看到了一些关于goroutine调度的句子,但我不理解它们。所以我猜我有两个问题:

1)为什么会阻塞?

2)如何使其不阻塞而什么都不做?

英文:

Let's take this example from the GoTour, as it illustrates my problem with processing SDL events only when there are events.

package main

import (
&quot;fmt&quot;
&quot;time&quot;
)

func main() {
tick := time.Tick(1e8)
boom := time.After(5e8)
for {
	select {
	case &lt;-tick:
		fmt.Println(&quot;tick.&quot;)
	case &lt;-boom:
		fmt.Println(&quot;BOOM!&quot;)
		return
	default:
		fmt.Println(&quot;    .&quot;)
		time.Sleep(5e7)
	}
}
}

This works. But what if I don't want to print or sleep in the default case, but just want to keep looping? I tried this:

	case &lt;-boom:
		fmt.Println(&quot;BOOM!&quot;)
		return
	default: // Nothing here.
	}
}
}

but it blocks.

I have seen here and there a sentence about goroutines scheduling, but I didn't understand them. So I guess I have two questions:

  1. Why does it block?

  2. How do I make it do nothing without blocking?

答案1

得分: 6

你的原始示例产生了这个结果

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!

而你的第二个示例产生了这个结果

[process took too long]

区别在于你在default情况下做了什么。default情况下的代码总是准备好运行的,所以带有default语句的select语句永远不会阻塞。第二个示例在循环中不断选择一个准备好运行的分支(case或default)。你现在想知道为什么计时器从未触发。这是因为Go协程不是抢占式调度的。因此,由于下面的循环没有进行任何IO操作,时间计时器永远不会触发。

for {
    select {
        // 任何操作
        default:
    }
}

有几种方法可以解决这个问题。首先,你可以像在第一个示例中那样添加一些IO操作。或者你可以添加一个runtime.Gosched()。或者你可以允许Go运行时使用多个线程,使用runtime.GOMAXPROCS(2),所有这些方法都可以解决问题。

在我看来,最好的方法是完全省略default语句像这样。没有default语句的select语句将会阻塞,直到其中一个case语句准备好运行。如果你想要做一些后台处理(你在default语句中做的),那么启动一个goroutine是最好的方式!

事实上,我见过很多在select语句中使用default导致的问题,我会倾向于说永远不要使用它们。

英文:

Your Original Example produces this

    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
    .
    .
tick.
BOOM!

Wheraeas Your Second Example produces this

[process took too long]

The difference is what you did in the default case. A default case is always ready to run so a select with a default statement in it never blocks. The second example runs round the loop continuously choosing one of the branches (case or default) that is ready to run. You are now wondering why the the timer never fires. That is because go routines are not pre-emptively scheduled. So because the loop below never does any IO, the time ticks never fire.

for {
    select {
        // whatever
        default:
    }
}

There are a number of ways of fixing this. Firstly you can put some IO in like you did in your first example. Or you could put a runtime.Gosched() in. Or you could allow the go runtime to use more than one thread with runtime.GOMAXPROCS(2) all of which will work.

The best way IMHO is to leave out the default statement entirely like this. A select without a default statement will block until one of the case statements is ready. If you want to do some background processing (that you were doing in the default statement) then start a goroutine - that is the go way!

In fact I've seen so many problems with default in select statements that I'd be tempted to say to never use them.

huangapple
  • 本文由 发表于 2013年2月1日 03:13:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/14633373.html
匿名

发表评论

匿名网友

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

确定