Go – high performance communication between goroutines?

Issue

I am trying to write a small game server in Go. I basically copied example from WebSocket library (https://github.com/gorilla/websocket).

Every second, servers executes function s.tick() which sends current time to the user. This works fine when users do not perform any actions.

When a user joins, leaves or sends a message, information is send over one of three channels (one for each action) and corresponding action is executed, however s.tick() is either skipping or delayed. I have noticed, that if users perform actions all the time, s.tick() will never be called.

Here’s the output:

Mon, 05 Dec 2016 20:19:11 CET
Mon, 05 Dec 2016 20:19:12 CET
Mon, 05 Dec 2016 20:19:13 CET
Mon, 05 Dec 2016 20:19:15 CET
Mon, 05 Dec 2016 20:19:16 CET
Mon, 05 Dec 2016 20:19:17 CET

-- users joining and leaving

Mon, 05 Dec 2016 20:19:25 CET
Mon, 05 Dec 2016 20:19:26 CET
Mon, 05 Dec 2016 20:19:27 CET
Mon, 05 Dec 2016 20:19:28 CET
Mon, 05 Dec 2016 20:19:29 CET

I tried to check what is causing so much delay between the actions (none of these actions is taking long time) and my only thought is that:

  • my code is just bad (very likely)
  • Go’s channels are slow

I checked time at the beginning of a loop and at the beginning of a case clause and it is often taking more than 200ms to even receive data from channel. Having said that, how can I improve the performance of this solution? I can’t seem to be able to have server working well at one tick per second, let alone 60.

Below’s snippet of my code:

type GameServer struct {
    players map[*Player]bool

    register   chan *Player
    unregister chan *Player

    broadcast chan []byte
}

func (s *GameServer) broadcastMessage(msg []byte) {
    for player := range s.players {
        player.messages <- msg
    }
}

func (s *GameServer) tick() {
    s.broadcastMessage([]byte(time.Now().Format(time.RFC1123)))
}

// question is mostly related to this function
func (s *GameServer) run() {
    for {
        select {
        case _ = <-time.NewTicker(time.Second).C:
            s.tick()
        case client := <-s.register:
            s.players[client] = true
        case client := <-s.unregister:
            delete(s.players, client)
        case msg := <-s.broadcast:
            s.broadcastMessage(msg)
        }
    }
}

Solution

try this:

type GameServer struct {
    players map[*Player]bool

    register   chan *Player
    unregister chan *Player

    broadcast chan []byte

    ticker *time.Ticker //use a single "global" *time.Ticker

}

func (s *GameServer) broadcastMessage(msg []byte) {
    for player := range s.players {
        player.messages <- msg
    }
}

func (s *GameServer) tick() {
    s.broadcastMessage([]byte(time.Now().Format(time.RFC1123)))
}

// question is mostly related to this function
func (s *GameServer) run() {
    for {
        select {
        case _ = <- s.Ticker.C: //use the "global" *time.Ticker instead of creating a new one every time
            s.tick()
        case client := <-s.register:
            s.players[client] = true
        case client := <-s.unregister:
            delete(s.players, client)
        case msg := <-s.broadcast:
            s.broadcastMessage(msg)
        }
    }
}

I don’t think you want to be creating a new time.Ticker every call to run. The above should fix your problems.

Answered By – Daniel Robinson

Answer Checked By – Candace Johnson (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.