【问题标题】:Observer pattern or a Publish/Subscribe pattern for a cellular automaton元胞自动机的观察者模式或发布/订阅模式
【发布时间】:2021-12-26 01:11:23
【问题描述】:

我正在尝试为一种元胞自动机编写观察者模式或发布/提交模式。

经典的观察者模式并不适用,因为如果单元 A 订阅单元 B 中的更改,反之亦然,由于递归方法,应用程序将耗尽堆栈(B.update() 将调用 A .update() 等,应用程序将耗尽堆栈)。

所以我考虑使用发布/订阅模式,其中各个单元格相互传递消息,而不是调用彼此的 update() 方法。

这是一个简单的例子,有两个单元格 A 和 B:

package main

import (
    "fmt"
    ps "publish/pubsub"
)

func main() {

    fmt.Printf("Starting\n")

    chEnd := make(chan int)

    // initialize
    a := ps.NewNode(1, 0)
    b := ps.NewNode(2, 0)

    // connect nodes
    a.Connect(b.ChOut)
    b.Connect(a.ChOut)

    // Start listening
    a.Listen()
    b.Listen()

    // Start sending data on one arbitrary node
    // to start the process.
    a.ChIn <- 10

    <-chEnd
}

以及对应的库

package pubsub

import (
    "fmt"
)

type Node struct {
    Id    int
    State int
    ChOut chan int
    ChIn  chan int
}

func NewNode(id int, state int) Node {
    chout := make(chan int)
    var chin chan int
    return Node{id, state, chout, chin}
}

func (p *Node) Broadcast(inItem int) {
    p.ChOut <- inItem + 1
    //time.Sleep(100 * time.Millisecond)
}

func (p *Node) Listen() {
    go func() {
        for {
            select {
            case inItem := <-p.ChIn:
                fmt.Printf("%d: %d\n", p.Id, inItem)
                p.Broadcast(inItem)
            }
        }
    }()
}

func (p *Node) Connect(ch chan int) {
    p.ChIn = ch
}

每个节点都有一个输入和一个输出通道。 B的输入通道是A的输出通道,反之亦然。

每次更新都只是增加另一个单元格传递的数据。

它似乎工作。到目前为止,一切顺利。

我尝试用一​​组 4 个单元 A、B、C、D 做类似的事情,以模拟各种类型的一维元胞自动机。

在第二次尝试中, 每个单元都有两个输入通道(let 和 right)来分别监听其最近的左手和右手邻居(ChinL 和 ChinR)。 每个单元必须输出通道以将其最新更新的状态传达给其最近的邻居(ChoutL 和 ChoutR)。 在使用 4 个单元格执行该方案时,我一定做错了什么,因为它产生了奇怪的结果:在 4 个单元格之间来回传递的值似乎达到了阈值,而不是在每个连续步骤中都增加:这是代码:

package main

import (
    "fmt"
    ps "publish/pubsub"
)

func main() {

    fmt.Printf("Starting\n")

    chEnd := make(chan int)

    // initialize
    a := ps.NewNode(1, 0)
    b := ps.NewNode(2, 0)
    c := ps.NewNode(3, 0)
    d := ps.NewNode(4, 0)

    // connect nodes
    a.ChInL = d.ChOutR
    a.ChInR = b.ChOutL

    b.ChInL = a.ChOutR
    b.ChInR = c.ChOutL

    c.ChInL = b.ChOutR
    c.ChInR = d.ChOutL

    d.ChInL = c.ChOutR
    d.ChInR = a.ChOutL

    // Start listening
    go a.Listen()
    go b.Listen()
    go c.Listen()
    go d.Listen()

    go a.Broadcast()
    go b.Broadcast()
    go c.Broadcast()
    go d.Broadcast()

    // Start sending data on one arbitrary node
    // to start the process.
    a.ChInL <- 1

    // Dummy read on channel to make main() wait
    <-chEnd
}

/*
    A   B   C   D
    LR  LR  LR  LR
*/

以及对应的库


package pubsub

import (
    "fmt"
    "strings"
)

type Node struct {
    Id     int
    State  int
    ChOutL chan int
    ChOutR chan int
    ChInL  chan int
    ChInR  chan int
    ChIO   chan int
}

func NewNode(id int, state int) Node {
    choutL := make(chan int)
    choutR := make(chan int)
    var chinL chan int
    var chinR chan int
    chIO := make(chan int)
    return Node{id, state, choutL, choutR, chinL, chinR, chIO}
}

func (p *Node) Broadcast() {
    for item := range p.ChIO {
        p.ChOutL <- item + 1
        p.ChOutR <- item + 1
        fmt.Printf("%d: %d %s\n", p.Id, item, strings.Repeat("*", item))
    }
}

func (p *Node) Listen() {
    for {
        //time.Sleep(100 * time.Millisecond)
        select {
        case inItem := <-p.ChInL:
            go func() {
                p.ChIO <- inItem
            }()
        case inItem := <-p.ChInR:
            go func() {
                p.ChIO <- inItem
            }()
        }
    }
}

为了完整起见,这里是上述模块的 go.mod:

module publish

go 1.17

【问题讨论】:

    标签: go publish-subscribe observer-pattern channels


    【解决方案1】:

    对于节点在p.ChInLp.ChInR 上接收到的每个信号,您会发出 2 个信号。第一个给p.ChOutL,第二个给p.ChOutR。由于每个节点都这样做,因此会产生指数级数量的信号。

    在本地运行时,阈值在 20 到 25 之间。所以大约有 2^25 33554432 信号在四处传播。要查看 26,程序需要处理 67108864 信号。所以它会超过这个阈值,只是指数级地慢。

    现在进行修复。我认为你应该实现某种tick 系统。因此,您不必为自动机的每次更改发送更新信号,而是每tick 发送一个更新,例如每秒 20 次。

    也许更好,而不是使用例程和通道,只需制作一个节点切片并在它们上循环(同样只更新邻居而不是在同一个滴答/循环中传播它)。循环的每次迭代,所有节点的状态都发生了变化。我相信这也是其他机器人(如现场游戏)的工作方式。现在您可以像计算机一样快地运行模拟了。

    【讨论】:

    • 我试图模仿的基本上是以下想法:4 台服务器,相互监听(到最近的邻居)并做出相应的反应,以此类推,递归。
    • 基本上,我试图不使用生命游戏(时间片)中使用的经典方法,其中节点数组每次“扫描”或每个周期仅更新一次。
    • 您必须记住,递归必须始终有一个基本情况,即您不再继续向其他邻居开具发票的时刻。否则,在递归的情况下或使用通道时此问题中的行为会导致堆栈溢出。 runestone.academy/runestone/books/published/pythonds/Recursion/…
    • 这就是我不使用观察者模式的原因。
    • 您当前的模式可以工作,观察者模式也可以。你不能创造比你接收到的更多的信号,这取决于你想如何解决这个问题。这就是为什么您的第一个示例确实有效但第二个示例无效,因为在第一个示例中,您只为收到的每个信号发送 1 个信号,因此信号的净总数保持为 0。
    【解决方案2】:

    这是一种可能的选择:

    package pubsub
    
    import (
        "fmt"
        "math/rand"
        "strings"
        "time"
    )
    
    type Node struct {
        Id     int
        State  int
        ChOutL chan int
        ChOutR chan int
        ChInL  chan int
        ChInR  chan int
        ChIO   chan int
    }
    
    func NewNode(id int, state int) Node {
        choutL := make(chan int)
        choutR := make(chan int)
        var chinL chan int
        var chinR chan int
        chIO := make(chan int)
        return Node{id, state, choutL, choutR, chinL, chinR, chIO}
    }
    
    func (p *Node) Broadcast() {
        for item := range p.ChIO {
            rnd := rand.Intn(2)
            if rnd == 0 {
                p.ChOutL <- item + 1
            } else {
                p.ChOutR <- item + 1
            }
            fmt.Printf("%d: %d %s\n", p.Id, item, strings.Repeat("*", item))
        }
    }
    
    func (p *Node) Listen() {
        for {
            time.Sleep(100 * time.Millisecond)
            select {
            case inItem := <-p.ChInL:
                p.ChIO <- inItem
            case inItem := <-p.ChInR:
                p.ChIO <- inItem
            }
        }
    }
    

    【讨论】:

      【解决方案3】:

      这是第二种选择:

      package main
      
      import (
          "fmt"
          ps "pub/pubsub"
      )
      
      func main() {
      
          fmt.Printf("Hello\n")
      
          chEnd := make(chan int)
      
          A := ps.NewNode(1, 0)
          B := ps.NewNode(2, 0)
          C := ps.NewNode(3, 0)
          D := ps.NewNode(4, 0)
      
          B.LeftNode = &A
          B.RightNode = &C
          C.LeftNode = &B
          C.RightNode = &D
          D.LeftNode = &C
          D.RightNode = &A
          A.LeftNode = &D
          A.RightNode = &B
      
          A.Listen()
          B.Listen()
          C.Listen()
          D.Listen()
      
          A.State = 1
          <-chEnd
      
      }
      
      //----
      
      
      package pubsub
      
      import (
          "fmt"
          "strings"
          "sync"
          "time"
      )
      
      type Node struct {
          Id         int
          State      int
          LeftNode   *Node
          RightNode  *Node
          LeftState  int
          RightState int
      }
      
      var m sync.Mutex
      
      func NewNode(id int, state int) Node {
          return Node{id, state, nil, nil, 0, 0}
      }
      
      func (n *Node) Listen() {
          go func() {
              for {
                  m.Lock()
                  time.Sleep(10 * time.Millisecond)
                  if n.LeftState != n.LeftNode.State {
                      n.LeftState = n.LeftNode.State
                      n.State = n.LeftNode.State + 1
                      fmt.Printf("%d: %d %s\n", n.Id, n.State, strings.Repeat("*", n.State))
                  }
                  m.Unlock()
              }
          }()
          go func() {
              for {
                  m.Lock()
                  time.Sleep(10 * time.Millisecond)
                  if n.RightState != n.RightNode.State {
                      n.RightState = n.RightNode.State
                      n.State = n.RightNode.State + 1
                      fmt.Printf("%d: %d %s\n", n.Id, n.State, strings.Repeat("*", n.State))
                  }
                  m.Unlock()
              }
          }()
      }
      
      
      
      

      【讨论】:

        猜你喜欢
        • 2019-11-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-12-14
        • 1970-01-01
        • 2012-07-23
        • 2014-02-06
        相关资源
        最近更新 更多