How Go’s net Package Powers High‑Performance Networking: Inside Listen, Accept, Read & Write

This article dissects Go's net package, showing how its Listen, Accept, Read and Write functions combine goroutines with epoll to deliver synchronous‑style code that avoids costly thread switches, and walks through the underlying source code and runtime mechanisms step by step.

Refining Core Development Skills
Refining Core Development Skills
Refining Core Development Skills
How Go’s net Package Powers High‑Performance Networking: Inside Listen, Accept, Read & Write

1. Using the Go net package

A minimal server built with the official net package demonstrates the core flow: create a listener, accept connections, and handle each connection in a separate goroutine.

func main() {
    listener, _ := net.Listen("tcp", "127.0.0.1:9008")
    for {
        conn, err := listener.Accept()
        go process(conn)
    }
}

func process(conn net.Conn) {
    defer conn.Close()
    var buf [1024]byte
    _, _ = conn.Read(buf[:])
    _, _ = conn.Write([]byte("I am server!"))
    // ...
}

The program appears synchronous, but each Accept, Read and Write may block the current goroutine, not the OS thread.

2. Listen implementation

Unlike C/Java where listen directly invokes the kernel syscall, Go's net.Listen wraps several steps:

Create a non‑blocking socket.

Bind the socket to a local address.

Call the kernel listen syscall.

Create an epoll object.

Add the listening socket to epoll for incoming connections.

This high‑level wrapper hides the multiple underlying system calls.

2.1 Listen entry point

func Listen(network, address string) (Listener, error) {
    var lc ListenConfig
    return lc.Listen(context.Background(), network, address)
}

The call then reaches ListenConfig.Listen, which for TCP routes to listenTCP and eventually to the low‑level socket function.

2.2 Socket creation (sysSocket)

func sysSocket(family, sotype, proto int) (int, error) {
    s, err := socketFunc(family, sotype, proto) // raw socket syscall
    syscall.SetNonblock(s, true)               // make it non‑blocking
    return s, err
}

2.3 Bind and listen (listenStream)

func (fd *netFD) listenStream(laddr sockaddr, ...) error {
    syscall.Bind(fd.pfd.Sysfd, lsa)          // bind
    listenFunc(fd.pfd.Sysfd, backlog)        // listen
    if err := fd.init(); err != nil {       // epoll setup
        return err
    }
    return nil
}

2.4 Epoll creation and registration

func (pd *pollDesc) init(fd *FD) error {
    ctx, errno := runtime_pollOpen(uintptr(fd.Sysfd)) // add fd to epoll
    // ...
    return nil
}

The runtime creates an epoll instance (via epollcreate1) and registers the listening socket.

3. Accept process

Invoke the kernel accept syscall.

If no connection is pending, block the current goroutine.

When a connection arrives, add its socket to epoll and return the new netFD.

3.1 Accept a connection

func (ln *TCPListener) Accept() (Conn, error) {
    c, err := ln.fd.accept()
    return c, err
}

func (fd *netFD) accept() (netfd *netFD, err error) {
    d, rsa, errcall, err := fd.pfd.Accept()
    netfd, err = newFD(d, fd.family, fd.sotype, fd.net)
    netfd.init()
    return netfd, err
}

3.2 Blocking the goroutine

if err == syscall.EAGAIN && fd.pd.pollable() {
    if err = fd.pd.waitRead(fd.isFile); err == nil {
        continue // goroutine parked until readable event
    }
}

The runtime uses pollDesc.waitRead which eventually calls runtime_pollWait and parks the goroutine via gopark.

3.3 Adding the new connection to epoll

func (fd *netFD) accept() (netfd *netFD, err error) {
    // ... after accept succeeds
    netfd, err = newFD(d, fd.family, fd.sotype, fd.net)
    netfd.init() // registers the new socket with epoll
    return netfd, err
}

4. Read and Write internals

4.1 Read flow

func (c *conn) Read(b []byte) (int, error) {
    return c.fd.Read(b)
}

func (fd *FD) Read(p []byte) (int, error) {
    for {
        n, err := syscall.Read(fd.Sysfd, p)
        if err == syscall.EAGAIN && fd.pd.pollable() {
            if err = fd.pd.waitRead(fd.isFile); err == nil {
                continue // block until data is available
            }
        }
        return n, err
    }
}

4.2 Write flow

func (c *conn) Write(b []byte) (int, error) {
    return c.fd.Write(b)
}

func (fd *FD) Write(p []byte) (int, error) {
    for {
        n, err := syscall.Write(fd.Sysfd, p)
        if err == syscall.EAGAIN && fd.pd.pollable() {
            if err = fd.pd.waitWrite(fd.isFile); err == nil {
                continue // block until socket is writable
            }
        }
        return n, err
    }
}

5. Goroutine wake‑up (sysmon)

The runtime launches a periodic monitor goroutine sysmon. It repeatedly calls netpoll, which invokes epoll_wait on the epoll set. When events are ready, the corresponding pollDesc is retrieved and the blocked goroutine is moved to the runnable queue.

func sysmon() {
    for {
        list := netpoll(0) // block until at least one fd is ready
        // process ready descriptors
    }
}

Inside netpoll:

n := epollwait(epfd, &events[0], int32(len(events)), waitms)
for i := int32(0); i < n; i++ {
    var mode int32
    if ev.events&(_EPOLLIN|_EPOLLRDHUP|_EPOLLHUP|_EPOLLERR) != 0 { mode += 'r' }
    if ev.events&(_EPOLLOUT|_EPOLLHUP|_EPOLLERR) != 0 { mode += 'w' }
    if mode != 0 {
        pd := *(**pollDesc)(unsafe.Pointer(&ev.data))
        netpollready(&toRun, pd, mode)
    }
}
netpollready

calls netpollunblock to unpark the goroutine for read, write, or both.

Conclusion

Go’s networking stack presents a synchronous‑style API while internally leveraging non‑blocking sockets, epoll, and lightweight goroutine scheduling. This design eliminates the heavy thread‑context‑switch overhead of traditional blocking I/O, achieving high throughput with code that remains easy to read and maintain.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendGonetwork programmingepollGoroutineasynchronous-ionet package
Refining Core Development Skills
Written by

Refining Core Development Skills

Fei has over 10 years of development experience at Tencent and Sogou. Through this account, he shares his deep insights on performance.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.