5.3 A bi-directional data pump

If one is to write a program to pump data in a bi-directional way, it can be done by a non-blocking read/write method. This solution, however, is a busy-wait method.

An alternative is to use the select call to monitor changes to file descriptors. While the select based solution is very efficient, it requires a bit more work to keep track of the state of each pump. Furthermore, it also makes any processing of stream data very difficult.

The same problem can be solved easily by a multithreaded program (snippet):

struct Pump 
{ 
  int inFD; 
  int outFD; 
}
 
void pump(void *param) 
{ 
  int r; 
  char ch; 
 
  do 
  { 
    r = read(((struct Pump *)param)->inFD, &ch, 1); 
    write(((struct Pump *)param)->outFD, &ch, 1); 
  } 
  while (r == 1); 
} 
 
int main(void
{ 
  int pair1InFD, pair1OutFD; 
  int pair2InFD, pair2OutFD; 
  struct Pump pair1, pair2; 
  pthread_t pump1, pump2; 
 
  /* write some code to open the FDs */ 
  pair1.inFD = pair1InFD; 
  pair1.outFD = pair1OutFD; 
  pair2.inFD = pair2InFD; 
  pair2.outFD = pair2OutFD; 
 
  if ((pthread_create(&pump1, NULL, pump, &pair1) == 0) && 
      (pthread_create(&pump2, NULL, pump, &pair2) == 0)) 
  { 
    /* now pumping in both directions */ 
    /* the main thread can do something else, or just wait */ 
    if ((pthread_join(pump1, NULL) == 0) && 
        (pthread_join(pump2, NULL) == 0)) 
    { 
      /* now both pumps have ceased due to EOF of the in-side */ 
      /* the main thread can do some more processing */ 
    } 
  } 
  /* the error handling of pthread_create and pthread_join can use a 
     bit more work, like the use of pthread_cancel */ 
  return 0; 
}

This program is special because it makes use of the parameter of the function that defines the code of a thread. In this case, two threads are created to execute the code of the same function, pump. Each thread is passed a different parameter, however. Each parameter is a pointer to a struct Pump that defines the file descriptors of the incoming and outgoing file descriptors.

Compared to a single threaded solution that uses select, the multi-threaded solution does consume more resources. However, the multi-threaded solution also makes it trivial to add any processing logic to generate the output from the input. A single threaded solution based on select requires explicit state machines to emulate the concurrent logic of the two pumps.