Pth and ncurses

I've learned how to use the curses/ncurses library recently, and I decided to experiment with threading while using ncurses. This posed another learning opportunity: a chance to learn more about GNU Pth, a non-preemptive, userspace threading library. It's a really cool trick to get threading into any C program on any platform. I've used POSIX threads, Pthreads, before, so Pth is totally new to me.

The idea was this: a ticking timestamp string that I can move around the screen with the arrow keys. One thread will keep the clock up to date, and the other listens for user input and changes the clock coordinates.

The ncurses function for getting a key from the user is getch(). By default, it blocks waiting for the user to press a key, which is returned. Unfortunately, this doesn't interact with Pth well at all.

Pth threads are userspace threads rather than system threads. This means the operating system kernel is completely unaware of them, so they are managed by a userspace scheduler and the Pth threads all run inside a single system thread. Because of this, Pth threads can never take advantage of hardware parallelism. This disadvantage comes with the advantage of portability (hence the name "portable threads"). It can be used on systems that provide no threading support.

Pth threads are also non-preemptive. This means the thread currently in control must eventually choose to yield control to other threads, cooperating with them. Preemptive threads take control from each other, so they never have to yield. Fortunately, the Pth library sneaks in implicit yielding, so you usually don't have to worry about this when using Pth. You can generally treat the Pth threads as if they were preemptive.

As I was saying, Pth wasn't behaving well with ncurses. When I called getch() my entire program, all threads, were getting blocked too, which defeats the whole purpose of threading. I switched to Pthreads to see if it was a mistake on my part, or an issue with Pth. Pthreads was working just fine, so I had to figure out what I was doing wrong with Pth.

I did manage to get Pth to behave the same was as the Pthreads, but it took two significant extra changes. Here's a code listing. There's also a mutex synchronized draw_clock() function not shown here. I've written it so that I could easily switch back and forth between the two threading libraries.

#include <stdio.h>
#include <ncurses.h>
#if USE_PTH
#include <pth.h>
#else
#include <unistd.h>
#include <pthread.h>
#endif

int clockx = 0, clocky = 0;

void *run_clock (void *v)
{
  while (1)
    {
      draw_clock ();
      sleep (1);
    }
  return v;
}

int main ()
{
  /* ncurses init */
  initscr ();
  noecho ();
  cbreak ();
  keypad (stdscr, TRUE);

#if USE_PTH
  halfdelay (1);
  pth_init ();
  pth_spawn (NULL, &run_clock, NULL);
#else
  pthread_t thread;
  pthread_create (&thread, NULL, &run_clock, NULL);
#endif

  int ch;
  while ((ch = getch ()) != 'q')
    {
#if USE_PTH
      pth_yield (NULL);
#endif
      switch (ch)
        {
        case ERR:
          continue;
        case 'h':
        case KEY_LEFT:
        /* more switch code here. */
        }
    }

#if USE_PTH
  pth_kill ();
#endif
  endwin ();
  return 0;
}

Notice the two (bolded) differences in the code between Pth and Pthreads, in the USE_PTH sections. The Pth implementation uses the halfdelay() function, which tells getch() to be less blocking. The argument tells it to return with an error if nothing happens within one tenth of a second. This means our main polling loop will execute about 10 times a second when nothing is happening.

The second change is an explicit yield, because Pth doesn't place any implicit yields in the loop. Without the yield the same problem remains.

I'm not happy with either of these changes. Making getch() behave like non-blocking input is very hackish. If I extend my program I'll always have to be careful when I call getch(), since it's (essentially) non-blocking. I'll also have to make sure I always yield when polling with getch(). So how do I fix this? First, let's see why we need that explicit yield. Pth is supposed to be hiding that from me.

How does Pth insert implicit yielding? I've been aware of Pth for years, and I've always wondered about this, but never bothered to look. I dug into the Pth sources.

Pth inserts yields before some of the common blocking operations. It has its own definitions for functions such as read(), write(), fork(), and system(). This is where the implicit yielding is injected. It steals your calls to these functions, using its own functions. Here's the relevant section of pth.h, where the "soft" version of this happens (the "hard" version uses syscall(), operating at a lower level),

#define fork          pth_fork
#define waitpid       pth_waitpid
#define system        pth_system
#define nanosleep     pth_nanosleep
#define usleep        pth_usleep
#define sleep         pth_sleep
#define sigprocmask   pth_sigmask
#define sigwait       pth_sigwait
#define select        pth_select
#define pselect       pth_pselect
#define poll          pth_poll
#define connect       pth_connect
#define accept        pth_accept
#define read          pth_read
#define write         pth_write
#define readv         pth_readv
#define writev        pth_writev
#define recv          pth_recv
#define send          pth_send
#define recvfrom      pth_recvfrom
#define sendto        pth_sendto
#define pread         pth_pread
#define pwrite        pth_pwrite

Its own functions wrap the real deal, but suspend themselves on an awaiting event and yield. Any time the scheduler runs, it polls for these events, using select() in the case of input/output, and will wake these threads back up if the required event occurs.

The problem with getch() is that Pth doesn't know about it, so it doesn't get a chance to handle it properly. After taking a look at the implementation for pth_read(), I fixed getch() for my program,

#if USE_PTH
int pth_getch ()
{
  pth_event_t ev;
  ev = pth_event (PTH_EVENT_FD | PTH_UNTIL_FD_READABLE, 0);
  pth_wait (ev);
  return getch ();
}
#undef getch
#define getch pth_getch
#endif

It tells the Pth scheduler that the thread wants to be suspended until there's something to read on stdin (file descriptor 0). This prevents the system thread that everyone is counting on from blocking. With this redefinition I went back and removed the two Pth additions, halfdelay() and the yield, and it now behaves exactly the same way as the Pthreads version. Fixed!

If you use any other libraries, you'll need to do this for any long-blocking function that Pth doesn't already catch.

If you want to see this in action, here's the full source: clock.c. You can choose the threading library to use,

gcc -lncurses -lpth -DUSE_PTH clock.c -o clock_pth
gcc -lncurses -pthread clock.c -o clock_pthread

Update: I've just discovered that Debian and Debian-based systems have implicit yeilding disabled by default. You may have needed to add this before the pth.h include.

#define PTH_SYSCALL_SOFT 1

This is now in the linked source.

Have a comment on this article? Start a discussion in my public inbox by sending an email to ~skeeto/public-inbox@lists.sr.ht [mailing list etiquette] , or see existing discussions.

null program

Chris Wellons

wellons@nullprogram.com (PGP)
~skeeto/public-inbox@lists.sr.ht (view)