英文:
Observer pattern or a Publish/Subscribe pattern for a cellular automaton
问题
我正在尝试为一种细胞自动机编写观察者模式或发布/订阅模式。
经典的观察者模式行不通,因为如果细胞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)
// 初始化
a := ps.NewNode(1, 0)
b := ps.NewNode(2, 0)
// 连接节点
a.Connect(b.ChOut)
b.Connect(a.ChOut)
// 开始监听
a.Listen()
b.Listen()
// 在一个任意节点上开始发送数据
// 以启动进程。
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来做类似的事情,以模拟一维细胞自动机。
在这第二次尝试中,
每个细胞都有两个输入通道(左和右)来监听其最近的左右邻居(ChinL和ChinR),
每个细胞都有两个输出通道,用于将其最新更新的状态传递给最近的邻居(ChoutL和ChoutR)。
在这个使用4个细胞的方案的实现中,我一定做错了什么,因为它产生了奇怪的结果:4个细胞之间来回传递的值似乎达到了一个阈值,而不是在每个连续的步骤中递增:以下是代码:
package main
import (
"fmt"
ps "publish/pubsub"
)
func main() {
fmt.Printf("Starting\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)
// 连接节点
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
// 开始监听
go a.Listen()
go b.Listen()
go c.Listen()
go d.Listen()
go a.Broadcast()
go b.Broadcast()
go c.Broadcast()
go d.Broadcast()
// 在一个任意节点上开始发送数据
// 以启动进程。
a.ChInL <- 1
// 读取通道上的虚拟数据,使main()等待
<-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
英文:
I am trying to code an observer pattern or a publish/submit pattern for a sort of cellular automaton.
The classical observer pattern does not to the trick because if a cell A subscribes to changes in a cell B and vice-versa, the application will run out of stack owing to the recursive approach (B.update() will call A.update() and so on and the app will run out of stack).
So I thought about using a publish/subscribe pattern where respective cells pass each other messages, rather than calling each other's update() methods.
Here is a simple example with two cells A and 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
}
and the corresponding lib
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
}
Each node has a input and an output channe.
The input channel of B is the output channel of A and vice-versa.
Every update consists merely of incrementing the data passed by the other cell.
It seems to work. So far, so good.
I tried to do something similar with a set of 4 cells A, B, C, D, in order to simulate a one dimensional cellular automaton of sorts.
In this second attempt,
each cell has two input channels (let and right) to listen to its closest left- and right-hand neighbour, respectively (ChinL and ChinR).
each cell has to output channels to communicate its latest updated state to its closest neighbours (ChoutL and ChoutR).
I must have done something wrong in the implementation of that scheme with 4 cells, because it yields odd results : the values passed back and forth between the 4 cells seem to hit a threshold instead of increasing at every consecutive step: here is the code:
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
*/
and the corresponding lib
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
}()
}
}
}
For completeness, here is the go.mod for the modules above:
module publish
go 1.17
答案1
得分: 4
对于节点在 p.ChInL
或 p.ChInR
上接收到的每个信号,你发送两个信号。第一个信号发送到 p.ChOutL
,第二个信号发送到 p.ChOutR
。由于每个节点都这样做,信号的数量将呈指数增长。
在本地运行时,阈值在20到25之间。因此大约有2^25(33554432)个信号在传递。要看到26,程序将需要处理67108864个信号。因此,它将超过这个阈值,但速度会指数级变慢。
现在来修复一下。我认为你应该实现一种 tick
系统。所以,不是在每次自动机发生变化时发送更新信号,而是每个 tick
发送一次更新信号,比如每秒20次。
或者更好的是,不使用例程和通道,只需创建一个节点的切片并循环遍历它们(同样只在同一个 tick
/循环中更新邻居而不传播)。循环的每次迭代都会改变所有节点的状态。我相信这也是其他自动机(如生命游戏)的工作原理。现在,你可以以计算机的最快速度运行模拟。
英文:
For every signal a node receives on p.ChInL
or p.ChInR
you send out 2 signals. The first to p.ChOutL
and the second to p.ChOutR
. Since every node does this there will be an exponential amount of signals going around.
When running it locally the threshold is between 20 and 25. So about 2^25 33554432
signals going around. To see 26, the program will need to process 67108864
signals. So it will go past this threshold, just exponentially slower.
Now for the fix. I think you should implement some sort of tick
system. So instead of sending update signals for every change to the automaton you send one update every tick
like 20 times per second.
Maybe better yet, instead of using routines and channels, just make an slice of nodes and loop over them (again only updating the neighbor and not propagating it in the same tick/loop). Each iteration of the loop the state of all nodes has changed. I believe this is also how other automatons like game-of-live work. Now you can run the simulation as fast as your computer can run.
答案2
得分: 0
这是一个可能的替代方案:
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
}
}
}
希望对你有帮助!
英文:
Here is one possible alternative:
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
得分: 0
这是第二个替代方案:
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()
}
}()
}
英文:
Here is a second alternative:
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()
}
}()
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论