How do I write unix select loop using goroutines?

622 views
Skip to first unread message

sandro....@gmail.com

unread,
Jun 3, 2016, 9:28:21 PM6/3/16
to golang-nuts
I want to write a go program that effectively blocks until a named pipe is ready to be read, then reads it, and blocks again. I understand that I can use a goroutine to read data from IO, and send it to the main goroutine using a channel—that makes sense to me. What I don't understand is how to consume 0 CPU while waiting for IO in the goroutine. When I call File.Read, I immediately receive an EOF, then the loop repeats causing the CPU to spike.

Here is the equivalent C code that I am trying to replicate:

# The file named "file" was created with mkfifo
# When I run `echo "hi" > file` I expect the C program to print out the characters in the file.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/socket.h>

int main(void) {
  printf("hello world\n");
  int fd = open("file", O_RDONLY);
  fd_set rd, wr, er;
  FD_ZERO(&rd);
  FD_ZERO(&wr);
  FD_ZERO(&er);
  FD_SET(fd, &rd);
  char data[1];

  for(;;) {
    printf("selecting\n");
    int zz = select(fd+1, &rd, &wr, &er, NULL);
    printf("select returned %i\n", zz);
    if (zz && FD_ISSET(fd, &rd)) {
      ssize_t size = read(fd, &data, 1);
      printf("read size %i\n", (int)(size));
      printf("data is %s %i\n", data, data[0]);
    }
  }

  return 1;
}

Cheers,
Sandro

Matt Harden

unread,
Jun 3, 2016, 10:09:19 PM6/3/16
to sandro....@gmail.com, golang-nuts
Don't try to replicate a select loop in Go. Just write the program as if you didn't have select; that is, just call Read in a loop and exit the loop at EOF. To do what the above code does, you don't need channels at all.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

sandro....@gmail.com

unread,
Jun 4, 2016, 12:10:25 AM6/4/16
to golang-nuts, sandro....@gmail.com
Matt,

Thanks for your reply. The problem is that my Go code receives an EOF immediately, before I even put data into the file. With the C code, I run the program in the foreground, then, using a second terminal, I write to the fifo. The C program prints what I wrote, and waits; then I write to fifo again, and the C program reads. I expect to be able to write to the file many times without restarting the program. If a minute of inactivity goes by, the program should use little resources while it's listening for a new write.

I can also implement this in Ruby, but I'm having trouble doing it properly in Go.

Matt Harden

unread,
Jun 4, 2016, 12:54:53 AM6/4/16
to sandro....@gmail.com, golang-nuts
Please post your code.

Matt Harden

unread,
Jun 4, 2016, 1:10:10 AM6/4/16
to sandro....@gmail.com, golang-nuts
The following program works for me to read from a FIFO called "file" and never goes into a busy wait state.


sandro....@gmail.com

unread,
Jun 4, 2016, 5:05:45 PM6/4/16
to golang-nuts, sandro....@gmail.com

Matt,


Thanks for the code example. I tried it, but unfortunately it pegs my CPU. I'm attaching a gif so you can see for yourself. Did I do something wrong?



sandro....@gmail.com

unread,
Jun 4, 2016, 5:12:53 PM6/4/16
to golang-nuts, sandro....@gmail.com
Matt, here's my Go code you requested:

package main

import (
"fmt"
"os"
"syscall"
)

func main() {
fd, _ := syscall.Open("file", syscall.O_RDONLY, 0)
file := os.NewFile(uintptr(fd), "../file")
b := make([]byte, 1)
bchan := make(chan string)
go func() {
for {
n, err := file.Read(b)
if err != nil {
if err.Error() != "EOF" {
fmt.Printf("%#v%s\n", err, err.Error())
}
} else if n > 0 {
bchan <- string(b)
}
}
}()
for {
select {
case data := <-bchan:
fmt.Println("data is", data)

Alex Bligh

unread,
Jun 4, 2016, 6:57:00 PM6/4/16
to sandro....@gmail.com, Alex Bligh, golang-nuts

On 4 Jun 2016, at 22:12, sandro....@gmail.com wrote:

> Matt, here's my Go code you requested:
>
> package main
>
> import (
> "fmt"
> "os"
> "syscall"
> )
>
> func main() {
> fd, _ := syscall.Open("file", syscall.O_RDONLY, 0)
> file := os.NewFile(uintptr(fd), "../file")
> b := make([]byte, 1)

why read a maximum of one byte at once

> bchan := make(chan string)
> go func() {
> for {
> n, err := file.Read(b)
> if err != nil {
> if err.Error() != "EOF" {
> fmt.Printf("%#v%s\n", err, err.Error())
> }

Surely you should have a break / return here?

Else at EOF (perhaps misdetected by not matching the above condition) you will
repeatedly loop. I expect that's what you might be seeing.

Alex

> } else if n > 0 {
> bchan <- string(b)
> }
> }
> }()
> for {
> select {
> case data := <-bchan:
> fmt.Println("data is", data)
> }
> }
> }
>
>
> On Saturday, June 4, 2016 at 2:05:45 PM UTC-7, sandro....@gmail.com wrote:
> Matt,
>
>
>
> Thanks for the code example. I tried it, but unfortunately it pegs my CPU. I'm attaching a gif so you can see for yourself. Did I do something wrong?
>
>
>
>
>
>
>
>
>
Alex Bligh




Matt Harden

unread,
Jun 4, 2016, 10:25:58 PM6/4/16
to sandro....@gmail.com, golang-nuts
That's weird. Perhaps FIFOs on MacOS/BSD behave differently than they do on Linux. Try moving the Open inside the loop as follows: https://play.golang.org/p/glO6TUZFsK. I also added a check for the error from io.Copy.

Also, if you start the program after creating the FIFO but before writing to it, does it busy wait then?
Reply all
Reply to author
Forward
0 new messages