Skip to content

Instantly share code, notes, and snippets.

@abatilo
Forked from jphsd/interprocess.md
Created October 6, 2024 05:01
Show Gist options
  • Save abatilo/ab40e886aaffa03a99dc35ca40249097 to your computer and use it in GitHub Desktop.
Save abatilo/ab40e886aaffa03a99dc35ca40249097 to your computer and use it in GitHub Desktop.
Interprocess channels in Go by using named pipes

How to use channels across different processes in Go

A couple of code samples to show how a named pipe can be used to extend Go's channel paradigm for use between different processes running on a system.

  • interprocess1.go details a single byte channel.
  • interprocess2.go details a channel that passes slices of bytes.

Note that opening a write channel will return two channels - one for writing to, and one which will be written to when the goroutine writing to the named pipe has completed. This last channel should be checked, prior to exiting a program, to avoid any loss of data.

The close() function should be called on the write channel once all data has been sent. This will cause any outstanding data and an EOF to be sent to the named pipe.

If you want to pass type instances over the named pipe then you will need to handle the encoding and decoding yourself.

Although intended for channel commnication between two Go processes, the other side of the named pipe can be any other process you like.

Note that named pipes can have multiple readers and writers although I don't recommend this as there are no guarantees about byte order and about who gets what.

// WriteChan opens the named pipe for writing and returns two channels,
// one for writing bytes to and one which will be written to when the writer goroutine completes.
func WriteChan(name string) (chan<- byte, <-chan byte, error) {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0o200)
if err != nil {
return nil, nil, err
}
c := make(chan byte)
q := make(chan byte)
go writer(f, c, q)
return c, q, nil
}
func writer(f *os.File, c chan byte, q chan<- byte) {
defer f.Close()
for b := range c {
n, err := f.Write([]byte{b})
if n != 1 {
panic(err)
}
}
q <- 0
}
// ReadChan opens the named pipe for reading and returns a channel from which bytes can be read.
func ReadChan(name string) (<-chan byte, error) {
f, err := os.OpenFile(name, os.O_RDONLY, 0o400)
if err != nil {
return nil, err
}
c := make(chan byte)
go reader(f, c)
return c, nil
}
func reader(f *os.File, c chan byte) {
defer f.Close()
buf := []byte{0}
for true {
n, err := f.Read(buf)
if n == 0 && err == io.EOF {
close(c)
break
}
if err != nil {
panic(err)
}
c <- buf[0]
}
}
// WriteChan opens the named pipe for writing and returns two channels,
// one for writing byte slices to and one which will be written to when the writer goroutine completes.
func WriteChan(name string) (chan<- []byte, <-chan byte, error) {
f, err := os.OpenFile(name, os.O_WRONLY|os.O_APPEND, 0o200)
if err != nil {
return nil, nil, err
}
c := make(chan []byte)
q := make(chan byte)
go writer(f, c, q)
return c, q, nil
}
func writer(f *os.File, c chan []byte, q chan<- byte) {
defer f.Close()
for b := range c {
_, err := f.Write(b)
if err != nil {
panic(err)
}
}
q <- 0
}
// ReadChan opens the named pipe for reading and returns a channel from which byte slices can be read.
func ReadChan(name string) (<-chan []byte, error) {
f, err := os.OpenFile(name, os.O_RDONLY, 0o400)
if err != nil {
return nil, err
}
c := make(chan []byte)
go reader(f, c)
return c, nil
}
func reader(f *os.File, c chan []byte) {
defer f.Close()
max := 1024 // attempt to read this many bytes
for true {
buf := make([]byte, max)
n, err := f.Read(buf)
if n == 0 && err == io.EOF {
close(c)
break
}
if err != nil {
panic(err)
}
buf = buf[:n]
c <- buf
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment