So I needed to call a bunch of C functions from Go. But CGO is rather inefficient. So I thought I should delegate the work to a C worker and transport the results back via shared memory.
The server component is written in C. It listens on a UNIX domain socket and waits for requests, which are read until EOF. Note that the connection must still be writable, so the read EOF must be produced by the client with a call to shutdown. The server computes what the client needs (in this case, just echoing things back), and creates a memfd, populates it with the result, and sends the file descriptor back to the client via SCM_RIGHTS.
The client component is written in Go. It connects to the server, sends a request with everything it can get from stdin, reads the response, and then reads the file descriptor. It then maps the shared memory into its address space and reads the result to stdout.
Most of the time you should just communicate via normal UNIX domain socket connections as requests/responses. I'm using shared memory here because for certain tasks it results in less memcpying.