Golang:当不应该退出时,选择语句会退出

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

Golang: select statement exits when it shouldn't

问题

我正在尝试创建一个程序,每隔第3秒、第8秒和第24秒分别打印出"吃饭"、"工作"和"睡觉"。以下是我的代码:

package main

import (
	"fmt"
	"time"
)

func Remind(text string, delay time.Duration) <-chan string { //channel only for receiving strings
	ch := make(chan string) // buffered/unbuffered?
	go func() {
		for {
			msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
			ch <- msg
			time.Sleep(delay) // waits according to specification
		}
	}()
	return ch
}

func main() {
	ch1 := Remind("吃饭", 1000*1000*1000*3) // 每隔三秒
	ch2 := Remind("工作", 1000*1000*1000*8) // 每隔八秒
	ch3 := Remind("睡觉", 1000*1000*1000*24) // 每隔24秒
	select { // 选择一个非空的通道。应该无限循环运行 (?)
	case rem1 := <-ch1:
		fmt.Println(rem1)
	case rem2 := <-ch2:
		fmt.Println(rem2)
	case rem3 := <-ch3:
		fmt.Println(rem3)
	}
}

问题在于,在打印出时间后紧接着的"吃饭"之后,程序立即停止运行。在我阅读的其他示例中,select语句会一直运行。为什么这次不会呢?

英文:

I'm trying to create a program that prints &quot;Eat&quot;, &quot;Work&quot;, &quot;Sleep&quot; every 3rd, 8th, and 24th second respectively. Here is my code:

package main

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

func Remind(text string, delay time.Duration) &lt;-chan string { //channel only for receiving strings
	ch := make(chan string) // buffered/unbuffered?
	go func() {
		for {
			msg := &quot;The time is &quot; + time.Now().Format(&quot;2006-01-02 15:04:05 &quot;) + text
			ch &lt;- msg
			time.Sleep(delay) // waits according to specification
		}
	}()
	return ch
}

func main() {
	ch1 := Remind(&quot;Eat&quot;, 1000*1000*1000*3) // every third second	
	ch2 := Remind(&quot;Work&quot;, 1000*1000*1000*8) // every eighth second
	ch3 := Remind(&quot;Sleep&quot;, 1000*1000*1000*24) // every 24th second
	select { // chooses one channel that is not empty. Should run forever (?)
		case rem1 := &lt;-ch1:
			fmt.Println(rem1)
		case rem2 := &lt;-ch2:
			fmt.Println(rem2)
		case rem3 := &lt;-ch3:
			fmt.Println(rem3)
	}
}

The problem with it is that it stops running immediately after printing the time followed by &quot;Eat&quot;. In other examples I have read, the select statement goes on forever. Why doesn't it now?

答案1

得分: 4

我不知道你在哪里看到select会无限循环的说法,但实际上并不是这样的。

一旦执行了一个caseselect语句就会结束。如果在case中没有任何通信操作可以执行,并且没有default分支,select会阻塞,直到任何一个通信操作可以执行为止。但是一旦执行了一个caseselect就不会重复执行。

从规范中阅读相关部分:Select语句

你可以将它放在一个无限循环的for中,使其无限重复:

for {
	select { // 选择一个非空的通道。应该会无限循环(?)
	case rem1 := <-ch1:
		fmt.Println(rem1)
	case rem2 := <-ch2:
		fmt.Println(rem2)
	case rem3 := <-ch3:
		fmt.Println(rem3)
	}
}

另外注意:

你可以更轻松地创建time.Duration值,使用time包中的常量:

ch1 := Remind("吃饭", 3*time.Second)    // 每3秒钟提醒一次
ch2 := Remind("工作", 8*time.Second)   // 每8秒钟提醒一次
ch3 := Remind("睡觉", 24*time.Second) // 每24秒钟提醒一次

你可能还想了解一下time.Ticker类型,它用于类似于你的Remind()函数的任务。

英文:

I don't know where you've read that the select goes on forever, but it doesn't.

Once a case is executed, the select statement is "done". If none of the communication operations specified in cases can proceed and there is no default branch, select will block, for as long as any of the com. ops can proceed. But once a case is executed, select does not repeat.

Read the relevant section from the spec: Select statements.

Put it in an endless for to make it repeat forever:

for {
	select { // chooses one channel that is not empty. Should run forever (?)
	case rem1 := &lt;-ch1:
		fmt.Println(rem1)
	case rem2 := &lt;-ch2:
		fmt.Println(rem2)
	case rem3 := &lt;-ch3:
		fmt.Println(rem3)
	}
}

As a sidenote:

You can create time.Duration values much easier, using constants from the time package:

ch1 := Remind(&quot;Eat&quot;, 3*time.Second)    // every third second
ch2 := Remind(&quot;Work&quot;, 8*time.Second)   // every eighth second
ch3 := Remind(&quot;Sleep&quot;, 24*time.Second) // every 24th second

You may also want to check out the time.Ticker type which is for tasks similar to your Remind() function.

答案2

得分: 2

在Go语言中,select语句与switch控制语句非常相似,有时也被称为通信开关。select用于监听通道上的传入数据,但也可能存在在通道上发送值的情况。简而言之,select用于在并发执行的goroutine中获取或发送值。

在你的示例中,因为你在主goroutine中执行当前时间,所以它总是被执行。但是,由于其他goroutine在select语句中执行,它们并不总是有机会被执行,因为一旦执行了一个case,通道就会阻塞。

select的作用:

  • 如果所有通道都被阻塞,它会等待直到有一个通道可以继续执行。
  • 如果有多个通道可以继续执行,它会随机选择一个。
  • 当所有通道操作都无法继续执行且存在default子句时,会执行default子句:default始终可运行(即准备执行)。

select语句中使用发送操作和default子句可以保证发送操作是非阻塞的!

要无限循环地运行,可以将其放在一个for循环中:

package main

import (
	"fmt"
	"time"
)

func Remind(text string, delay time.Duration) <-chan string { //channel only for receiving strings
	ch := make(chan string) // buffered/unbuffered?
	go func() {
		for {
			msg := "The time is " + time.Now().Format("2006-01-02 15:04:05 ") + text
			ch <- msg
			time.Sleep(delay) // waits according to specification
		}
	}()
	return ch
}

func main() {
	ch1 := Remind("Eat", 1000*1000*1000*3)   // 每3秒钟执行一次
	ch2 := Remind("Work", 1000*1000*1000*8)  // 每8秒钟执行一次
	ch3 := Remind("Sleep", 1000*1000*1000*24) // 每24秒钟执行一次
	for {
		select { // 选择一个非空的通道。应该无限循环运行(?)
		case rem1 := <-ch1:
			fmt.Println(rem1)
		case rem2 := <-ch2:
			fmt.Println(rem2)
		case rem3 := <-ch3:
			fmt.Println(rem3)
		}
	}
}

你可以在这里运行代码:http://play.golang.org/p/BuPqm3xsv6

英文:

select in Go mostly resembles the switch control statement and is sometimes called communication switch. select listens for incoming data on channels, but there could also be cases where a value is sent on a channel. In one word select is used to get or send values on concurrently executing goroutines.

In your example because you are executing the current time in the main goroutine, it's always executed. But because the other goroutines are executed in the select statement these are not always get the chance to be executed, because once a case is executed the channel blocks.

What select does:

  • if all are blocked, it waits until one can proceed
  • if multiple can proceed, it chooses one at random.
  • when none of the channel operations can proceed and the default clause is present, then this is executed: the default is always runnable (that is: ready to execute).

Using a send operation in a select statement with a default case guarantees that the send will be non-blocking!

To run forever use it in a for loop:

package main

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

func Remind(text string, delay time.Duration) &lt;-chan string { //channel only for receiving strings
    ch := make(chan string) // buffered/unbuffered?
    go func() {
        for {
            msg := &quot;The time is &quot; + time.Now().Format(&quot;2006-01-02 15:04:05 &quot;) + text
            ch &lt;- msg
            time.Sleep(delay) // waits according to specification
        }
    }()
    return ch
}

func main() {
    ch1 := Remind(&quot;Eat&quot;, 1000*1000*1000*3) // every third second    
    ch2 := Remind(&quot;Work&quot;, 1000*1000*1000*8) // every eighth second
    ch3 := Remind(&quot;Sleep&quot;, 1000*1000*1000*24) // every 24th second
    for {
    	select { // chooses one channel that is not empty. Should run forever (?)
        case rem1 := &lt;-ch1:
            fmt.Println(rem1)
        case rem2 := &lt;-ch2:
            fmt.Println(rem2)
        case rem3 := &lt;-ch3:
            fmt.Println(rem3)
    	}
    }
}

http://play.golang.org/p/BuPqm3xsv6

huangapple
  • 本文由 发表于 2016年3月31日 15:55:29
  • 转载请务必保留本文链接:https://go.coder-hub.com/36327358.html
匿名

发表评论

匿名网友

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

确定