What is the best practice to write a concurrent TCP server in Go?

6,755 views
Skip to first unread message

Jingcheng Zhang

unread,
Apr 27, 2012, 11:23:49 PM4/27/12
to golan...@googlegroups.com
Hello,

It is said that event-driven nonblocking model is not the preferred programming model in Go, so I use "one goroutine for one client" model, but is it OK to handle millions of concurrent goroutines in a server process?

And, how can I "select" millions of channel to see which goroutine has data received? The "select" statement can only select on predictable number of channels, not on a lot of unpredictable channels. And how can I "select" a TCP Connection (which is not a channel) to see if there is any data arrived? Is there any "design patterns" on concurrent programming in Go?

Thanks in advance.

--
Best regards,
Jingcheng Zhang
Beijing, P.R.China

John Asmuth

unread,
Apr 27, 2012, 11:29:21 PM4/27/12
to golan...@googlegroups.com


On Friday, April 27, 2012 11:23:49 PM UTC-4, Jingcheng Zhang wrote:
Hello,

It is said that event-driven nonblocking model is not the preferred programming model in Go, so I use "one goroutine for one client" model, but is it OK to handle millions of concurrent goroutines in a server process?

A goroutine itself is 4kb. So 1e6 goroutines would require 4gb of base memory. And then whatever your server needs per goroutine that you add.

Any machine that might be handling 1e6 concurrent connections should have well over 4gb of memory. 
 
And, how can I "select" millions of channel to see which goroutine has data received?

That's not how it works. You just try to .Read() in each goroutine, and the select is done under the hood. The select{} statement is for channel communication, specifically.

All IO in go is event driven under the hood, but as far as the code you write, looks linear. The Go runtime maintains a single thread that runs epoll or kqueue or whatever under the hood, and wakes up a goroutine when new data has arrived for that goroutine. 
 
The "select" statement can only select on predictable number of channels, not on a lot of unpredictable channels. And how can I "select" a TCP Connection (which is not a channel) to see if there is any data arrived? Is there any "design patterns" on concurrent programming in Go?

These problems you anticipate simply do not exist with go. Give it a shot!
 
Message has been deleted

Jingcheng Zhang

unread,
Apr 27, 2012, 11:59:28 PM4/27/12
to golang-nuts
Hello John and Peter,

Thanks for your replies, I'm a bit clear now. But, if I want to "select" two fds, say, two connections, to read from one connection and write to the other one, and the connection is full-duplex (or in other word - I want to implements a message exchanger), how can I do? Should I start another two goroutines to handle the two directions' communication?

Is it a pattern to avoid "select" on file descriptors, but start a goroutine to block on the fd, then passing received data to the server goroutine through a channel instead?

On Sat, Apr 28, 2012 at 11:45 AM, Peter Thrun <peter...@ymail.com> wrote:
And, how can I "select" millions of channel to see which goroutine has data received? The "select" statement can only select on predictable number of channels, not on a lot of unpredictable channels. And how can I "select" a TCP Connection (which is not a channel) to see if there is any data arrived? Is there any "design patterns" on concurrent programming in Go?

One common pattern is as follows:

- One goroutine per connection.
- Goroutine uses blocking read on connection.
- Goroutine either process data directly or sends the data to some other goroutine using a channel.

Here's an (uncompiled and untested) example showing how to read four byte integers from a connection and send them to a central goroutine using a channel.  

import (
"encoding/binary"
"log"
"net"
)

func reader(c net.Conn, ch chan<- int32) {
for {
var i int32
if err := binary.Read(c, binary.BigEndian, &i); err != nil {
log.Print(err)
return
}
ch <- i
}
}

The central goroutine reads from the single channel passed to each connection goroutines:

func central(ch <-chan int32) {
for i := range ch {
log.Print(i)
}
}

minux

unread,
Apr 28, 2012, 12:27:28 AM4/28/12
to Jingcheng Zhang, golang-nuts
On Sat, Apr 28, 2012 at 11:59 AM, Jingcheng Zhang <dio...@gmail.com> wrote:
Hello John and Peter,

Thanks for your replies, I'm a bit clear now. But, if I want to "select" two fds, say, two connections, to read from one connection and write to the other one, and the connection is full-duplex (or in other word - I want to implements a message exchanger), how can I do? Should I start another two goroutines to handle the two directions' communication?
yes, two goroutine for socket read and socket write. 

Is it a pattern to avoid "select" on file descriptors, but start a goroutine to block on the fd, then passing received data to the server goroutine through a channel instead?
it's much easier to program than using select/poll/epoll/kqueue, is it?
(in fact, you even can't use these syscalls yourself, because there is no way to get
a network connection's underlying fd)

Ian Lance Taylor

unread,
Apr 28, 2012, 12:33:59 AM4/28/12
to Jingcheng Zhang, golang-nuts
Jingcheng Zhang <dio...@gmail.com> writes:

> Thanks for your replies, I'm a bit clear now. But, if I want to "select"
> two fds, say, two connections, to read from one connection and write to the
> other one, and the connection is full-duplex (or in other word - I want to
> implements a message exchanger), how can I do? Should I start another two
> goroutines to handle the two directions' communication?

Typically yes.

> Is it a pattern to avoid "select" on file descriptors, but start a
> goroutine to block on the fd, then passing received data to the server
> goroutine through a channel instead?

It is extremely unusual for a Go program to use the select system call
on a set of file descriptors. The select statement in Go is completely
different from the select system call.

It is normal to use blocking reads in a goroutine. Whether to pass data
to some other goroutine via a channel depends on how your program is
structured. Sometimes that would be appropriate, sometimes not.

Ian
Message has been deleted

Sugu Sougoumarane

unread,
Apr 28, 2012, 1:27:45 PM4/28/12
to Peter Thrun, golan...@googlegroups.com
Have you looked at  http://golang.org/pkg/net/rpc/ ?

On Fri, Apr 27, 2012 at 9:42 PM, Peter Thrun <peter...@ymail.com> wrote:
I want to implements a message exchanger), how can I do? Should I start another two goroutines to handle the two directions' communication?
 
Yes.  

This example might be helpful: http://gary.beagledreams.com/page/go-websocket-chat.html  The example does not call the connection Read and Write methods directly, but it does show the overall flow of exchanging messages.

Reply all
Reply to author
Forward
0 new messages