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
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
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.
Notice the two (bolded) differences in the code between Pth and
Pthreads, in the
USE_PTH sections. The Pth implementation
halfdelay() function, which
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
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
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
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
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
the "soft" version of this happens (the "hard" version
syscall(), operating at a lower level),
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
getch() for my program,
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
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
have implicit yeilding disabled by default. You may have needed to
add this before the
This is now in the linked source.