Go 戈朗:让选择的州成为确定性的?
让我们仔细看看InGo的时间包:Go 戈朗:让选择的州成为确定性的?,go,Go,让我们仔细看看InGo的时间包: package main import ( "fmt" "time" ) func main() { ticker := time.NewTicker(time.Second) defer ticker.Stop() done := make(chan bool) go func() { time.Sleep(10 * time.Second) done <- true
package main
import (
"fmt"
"time"
)
func main() {
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
done := make(chan bool)
go func() {
time.Sleep(10 * time.Second)
done <- true
}()
for {
select {
case <-done:
fmt.Println("Done!")
return
case t := <-ticker.C:
fmt.Println("Current time: ", t)
}
}
}
当done和ticker.C频道同时就绪时,我们进入了Go非确定性行为的领域:
select
阻塞,直到其中一个案例可以运行,然后执行该案例。如果多个已准备就绪,则随机选择一个
我理解Go的设计原理,为什么select是不确定的。这主要归结为一个语言无法解决的问题,因为这样做通常很难,并且可能会导致用户在不知不觉中编写快速的代码,从而将优先选择和练习留给读者
让我们假设,无论出于何种原因,我希望在结束程序并打印完成之前,确保所有挂起的勾号都被消耗掉代码>。是否有一个通用的转换可以应用于这个简单的示例,使其具有确定性
我尝试添加另一个信号通道:
func main() {
ticker := time.NewTicker(time.Second)
stop := make(chan bool)
done := make(chan bool)
tick := make(chan time.Time)
go func() {
time.Sleep(1 * time.Second)
stop <- true
}()
go func() {
for t := range tick {
fmt.Println("Current time: ", t)
}
done <- true
}()
for {
select {
case <-stop:
ticker.Stop()
close(tick)
case t := <-ticker.C:
tick <- t
break
case <-done:
fmt.Println("Done!")
return
}
}
}
我们不能保证我们不会在收到最后一个滴答声的同时收到停止消息,所以我们只是将问题转移到一些当它“不正确”时会惊慌失措的事情上(这比无声地这样做稍微好一点)。如果我们nil
ed了滴答声通道,我们就会转到原来的情况。我们仍然有可能根本不打印勾号的情况,因为我们有可能在计时器有机会触发之前关闭计时器
一个现成的频道怎么样
func main() {
ticker := time.NewTicker(time.Second)
tick := make(chan time.Time)
ready := make(chan bool, 1)
stop := make(chan bool)
done := make(chan bool)
go func() {
time.Sleep(1 * time.Second)
<-ready
stop <- true
}()
go func() {
for t := range tick {
fmt.Println("Current time: ", t)
}
done <- true
}()
for {
select {
case <-stop:
ticker.Stop()
close(tick)
case t := <-ticker.C:
select {
case ready<-true:
break
default:
}
tick <- t
break
case <-done:
fmt.Println("Done!")
return
}
}
}
func main(){
股票代码:=time.NewTicker(time.Second)
勾选:=制造(成龙时间)
准备就绪:=制作(chan bool,1)
停止:=制造(chan bool)
完成:=制作(陈波)
go func(){
时间。睡眠(1*时间。秒)
除非应用程序在ticker的ready state和done频道之间具有某种已知的顺序,否则无法确保应用程序按照发送值的顺序处理来自频道的值
通过使用嵌套的select语句,应用程序可以确保在ticker.C
中排队的值在done
中的值之前被接收
for {
select {
case t := <-ticker.C:
fmt.Println("Current time: ", t)
default:
// ticker.C is not ready for commination, wait for both
// channels.
select {
case <-done:
fmt.Println("Done!")
return
case t := <-ticker.C:
fmt.Println("Current time: ", t)
}
}
}
如果在启用两个通道时需要选择一个通道而不是另一个通道,则可以执行嵌套选择。如果在选择开始时启用了两个通道,则这将选择高优先级通道而不是低优先级通道:
select {
case <-highPriority:
// Deal with it
default:
select {
case <-lowPriority:
// low priority channel
default:
}
}
选择{
案例这是一个有趣的问题。我试图想出一个不涉及select
s来控制通道处理的解决方案
相反,它使用带有通道数组的收集器结构作为一种扇入,来编排有序读取
这段代码远非完美。它只是一个人为的例子来说明我的想法。
主环路内的计时器用于模拟同时准备就绪的多个通道。在紧密环路中使用和/或与不同类型的通道一起使用需要进一步的工作
// process multiple ready channels in a specific order
type Collector struct {
chans []<-chan int
signals []chan struct{}
ready []bool
values []int
valuesLock sync.Mutex
}
func NewCollector(chans ...<-chan int) *Collector {
signals := make([]chan struct{}, len(chans))
for i := range chans {
signals[i] = make(chan struct{})
}
return &Collector{
chans: chans,
ready: make([]bool, len(chans)),
values: make([]int, len(chans)),
signals: signals,
}
}
func (c *Collector) Start() {
for chanIndex, inChan := range c.chans {
go c.startWorker(chanIndex, inChan)
}
}
func (c *Collector) startWorker(idx int, in <-chan int) {
for receivedValue := range in {
// https://stackoverflow.com/questions/49879322/can-i-concurrently-write-different-slice-elements
c.values[idx] = receivedValue
c.ready[idx] = true
<-c.signals[idx] // barrier to sync channel reads
}
}
func (c *Collector) Process() {
// may add here some additional signal channel to avoid busy loops
c.valuesLock.Lock()
for i, isReady := range c.ready {
if isReady {
fmt.Println(c.values[i])
c.ready[i] = false
}
}
c.valuesLock.Unlock()
// signal all threads to proceed.
// the default case skips those that didn't receive anything, thus are not waiting
for i := range c.signals {
select {
case c.signals[i] <- struct{}{}:
default:
}
}
}
func TestOrderedProcessing(t *testing.T) {
c1 := make(chan int)
c2 := make(chan int)
c3 := make(chan int)
collector := NewCollector(c1, c2, c3)
collector.Start()
for n := 0; n < 5; n++ {
c2 <- 2
c1 <- 1
c3 <- 3
<-time.NewTimer(500 * time.Millisecond).C
collector.Process()
}
// prints the following five times
// 1
// 2
// 3
}
//按特定顺序处理多个就绪通道
类型收集器结构{
chans[]你能提供一点你想要解决的问题的背景吗?只是为了理解整个情况(对我来说)。谢谢。我想我正在寻找一种通用模式来处理你想要以特定顺序处理多个就绪通道的场景。我现在看到的唯一解决方案是在某个片段中“注册”就绪通道(由互斥锁保护)然后选择均匀分布的通道或基于其计数器的通道(每个通道的使用量)。我认为select
周围的每个(?)解决方案仍然会有边缘情况,但我猜,对不起。“完成了!”在所有示例中,它似乎只能打印一次,但它在输出中出现多次。代码列表/输出是否正确?@标记完成出现两次表示完成频道和自动售票机频道同时准备就绪,它随机选择完成而不是自动售票机。您可以通过查看它跳过的时间戳来判断这是第二个。值得一提的是,如果没有一个频道有一个要消费的项目,那么它将是一个繁忙的循环。@zerkms你是对的。这不是一个好的解决方案。我想知道这是否可能。也许我应该更具体地提到它,但是嵌套选择不起作用,因为在第一次选择中,以及在sec中,股票代码可能没有准备好ond select比赛仍然是可能的。@dcow正如我在回答中提到的,嵌套的select会关闭窗口,使两个通道几乎同时进入就绪状态。如果这还不够好,应用程序的要求是什么?如果应用程序中有其他已知的事件顺序,则select必须遵循哦,那么你应该在问题中说明这一点。这更像是一个原则性的问题:我很好奇在golang中是否有一种实际的方法来处理这个问题。例如,使用POSIXselect
,你会收到一个准备好处理的文件描述符列表,你可以选择如何处理它们。golang中真的没有模拟吗?@dcow Go没有提供获取准备好通信的通道列表的方法。通过获取该列表,您试图解决什么问题?也许有一个很好的解决方案可以解决这个更高级别的问题。在我尝试编写确定性程序时,我发现自己陷入了相当复杂的代码中,我担心我可能会使事情变得更复杂比必要的复杂。如果我能对“取消”进行优先级排序,我的代码就会简单得多消息。但在最高层次上,我只是有点恼火Go的时间包提供了一个表面上简单的示例,实际上最终是不确定的。我看到很多Go示例代码看起来如此简单和正确,但实际上它的执行是不确定的。我不喜欢不确定的程序。。。
for {
select {
case t := <-ticker.C:
fmt.Println("Current time: ", t)
default:
// ticker.C is not ready for commination, wait for both
// channels.
select {
case <-done:
// Give communication on <-ticker.C one last
// opportunity before exiting.
select {
case t := <-ticker.C:
// Note that the ticker may have entered
// the ready state just after the done channel
// entered the state.
fmt.Println("Current time: ", t)
default:
}
fmt.Println("Done!")
return
case t := <-ticker.C:
fmt.Println("Current time: ", t)
}
}
}
select {
case <-highPriority:
// Deal with it
default:
select {
case <-lowPriority:
// low priority channel
default:
}
}
for _,channel:=range channels {
select {
case <-channel:
//
default:
}
}
// process multiple ready channels in a specific order
type Collector struct {
chans []<-chan int
signals []chan struct{}
ready []bool
values []int
valuesLock sync.Mutex
}
func NewCollector(chans ...<-chan int) *Collector {
signals := make([]chan struct{}, len(chans))
for i := range chans {
signals[i] = make(chan struct{})
}
return &Collector{
chans: chans,
ready: make([]bool, len(chans)),
values: make([]int, len(chans)),
signals: signals,
}
}
func (c *Collector) Start() {
for chanIndex, inChan := range c.chans {
go c.startWorker(chanIndex, inChan)
}
}
func (c *Collector) startWorker(idx int, in <-chan int) {
for receivedValue := range in {
// https://stackoverflow.com/questions/49879322/can-i-concurrently-write-different-slice-elements
c.values[idx] = receivedValue
c.ready[idx] = true
<-c.signals[idx] // barrier to sync channel reads
}
}
func (c *Collector) Process() {
// may add here some additional signal channel to avoid busy loops
c.valuesLock.Lock()
for i, isReady := range c.ready {
if isReady {
fmt.Println(c.values[i])
c.ready[i] = false
}
}
c.valuesLock.Unlock()
// signal all threads to proceed.
// the default case skips those that didn't receive anything, thus are not waiting
for i := range c.signals {
select {
case c.signals[i] <- struct{}{}:
default:
}
}
}
func TestOrderedProcessing(t *testing.T) {
c1 := make(chan int)
c2 := make(chan int)
c3 := make(chan int)
collector := NewCollector(c1, c2, c3)
collector.Start()
for n := 0; n < 5; n++ {
c2 <- 2
c1 <- 1
c3 <- 3
<-time.NewTimer(500 * time.Millisecond).C
collector.Process()
}
// prints the following five times
// 1
// 2
// 3
}