POSTS
Loops and References in Go
Code for the project mentioned is located here
So I was working on a thing in go, nothing too major just a little doohickey to refresh my go skills. As a student worker, I provide AV support for classrooms. We use a program called RoomView Express to monitor all the Crestron equipment, although pretty much all we care about is help requests. I didn’t feel like installing RoomView Express on my laptop, and I also wanted to be able to get help requests without having to walk all the way back to the office only to turn around and walk back. So, I took a peek at the network traffic and figured out how help requests were handled.
There is a bunch of bytes that RE sends to the equipment when opening a socket, then the equipment starts sending what appears to be its current state periodically. When a help request is sent, the equipment literally just sends “need help”, which made it rather straightforward to check if we had received a help request.
So the first thing to do on startup is to connect to all the rooms. For the go version, I just load from a CSV into an array of Room structs. Then, for each room I launch a thread that is responsible for handling communication with every room. Now, I wanted each room to keep track of whether it was actually connected or not, and then display that status. I decided to use Nuklear for the GUI, so every 30th of a second I was checking a master state object that held the array of Room structs. The important thing to note here is that the listeners are launched as go funcs.
Take the following code as an example:
package main
import (
"fmt"
"time"
)
func main() {
foos := []foo{foo{"asd"}, foo{"asdasd"}}
adjust(foos)
time.Sleep(time.Second * time.Duration(1))
}
func adjust(foos []foo) {
for _, f := range foos {
go print(f)
}
}
func print(f foo) {
fmt.Println(f)
}
type foo struct {
A string
}
This behaves as expected:
{asd}
{asdasd}
Program exited.
However if we pass a reference into print
:
package main
import (
"fmt"
"time"
)
func main() {
foos := []foo{foo{"asd"}, foo{"asdasd"}}
adjust(foos)
time.Sleep(time.Second * time.Duration(1))
}
func adjust(foos []foo) {
for _, f := range foos {
go print(&f)
}
}
//note that this takes a reference now!
func print(f *foo) {
fmt.Println(f)
}
type foo struct {
A string
}
with output:
&{asdasd}
&{asdasd}
Program exited.
We also get this result if we pass foos
by reference.
This is a problem! Originally, I was going to modify the array of Rooms in place, then the main loop would pick up those changes. This would work fine in say, C++. But it doesn’t really work out if all of the listeners end up with the same Room. My guess (with absolutely no research backing it) is that when you pass by reference when iterating over an array, you end up effectively passing a reference to the iterator, not a reference to the actual item.
So, I ended up using chans to alert the main loop to changes in the state of a given Room, tracking by the index of the Room in the array. This required more code and some brief contemplation of where to stick the actual handler which wasn’t super onerous, but it did cost me a few hours figuring out what was going on. This may be a pitfall for anyone coming to Go from C or C++, as unless I missed a line in the docs this isn’t really mentioned or intuitively obvious (I may be quite wrong here - it could be that I just suffered from temporary blindness when reading the docs).
It’s interesting to note that this problem does not occur if we run print
in the main thread. Not sure why this is, but it’s on my list of things to look into at some point. For now it’s enough to know for the next case.
Update 5/23/19
Finally got around to updating this. So it turns out that I was sort of correct when I guessed that it was effectively a pointer to the iterator not the object that got passed in. The reason it doesn’t happen if we call print
synchronously is because the print
function gets called before the contents of f
get replaced. Also turns out that what I should have done is load the slice with references, not values - this would have given me the expected behaviour. What’s interesting is that in C++, throwing pointers around is almost a default action, something that just happens naturally (at least, that’s how it feels for me). I guess pointers not featuring as prominently in Go vs C++ (at least in my experience, just comparing what I hear from people) short circuited some of my decision making.
Also if I had been a bit more creative with my googling I would have stumbled across this post and saved myself a couple hours, but that probably wouldn’t have been as much fun. I probably won’t bother converting the project to use the array of pointers since I’ve already a working version with chans. The chans also give me the ability to do more than just update the state of a room, which may be useful if I ever expand functionality.
Update to the update Yeah I went ahead and switched to using a slice of pointers and saving the chan for alerting to help requests and whatnot, which also lets me slim it down to just holding the room number for now. I could just make it a string chan but if I ever extend it, then there’s already the struct there.