Introduction to Pth

The goal of this practical is to introduce you to Pth, the GNU Portable threading library. It will walk you through how to use the library, including building it and creating a test program that links against it.

Contents


What is Pth?

Pth is the GNU Portable Threads library, a C library that provides multithreading capabilities to user-space processes. It is similar to pthreads (which you should be familiar with from CSSE2310), except it operates completely in user-space — the kernel is not aware of the multiple threads of execution inside the user-space process.

With Pth, multiple threads of execution can exist inside one process. The kernel only ‘sees’ this single process, and as such will schedule it as normal. When the process is given a scheduling slice by the kernel, one of the Pth threads that exist in the process will be run.

The original Pth library implements cooperative multithreading, but we have patched it to replace this with a preemptive scheduler. All of your work on the Pth library will be with this version, including assignment 1.

It is highly recommended that you read the manual page for Pth before using it, as it contains detailed information on the library and its behaviour, which will be an invaluable resource to you. You will find the Pth manual page here.


Building Pth

For this course we will be using version 2.0.7-preempt of Pth.

  1. Download Pth from the course website
  2. Download the build script
  3. Make the script executable with chmod +x build-pth.sh
  4. Run ./build-pth.sh --help for instructions

Running ./build-pth.sh --help should show you that you will need to run it with the --extract argument for the first time, to ensure the source tree is extracted, once this is done once you should not need to use --extract again.

Run the build script now, and take note of how long it takes to build. Now, run it again see how long it takes. Why do you think there is such a difference?

The bottom of the build script's output shows you the libraries that have been installed. You should see two: one with a .a extension, and one with a .so extension. .a is a statically-linkable library, that can be linked into a program at compile-time.

The .so file is a dynamic library, which allows a program to link against the library at runtime.

You will end up using both types of libraries during your Pth development time (but not in this prac).


A simple test program

We call into the Pth library in much the same way as any other C library, but there is one important distinction: we must ensure we don't make any system calls that block. Pth schedules threads entirely in user-space, without any help from the kernel, which means that the kernel has no way to know that there are other threads in our process which might be ready to run while we wait for our blocking system call to return. Instead the kernel will block all threads in our process, causing starvation.

Pth provides wrappers around most of the blocking system calls, and you must call these instead. They are prefixed with pth_ (for example, pth_sleep().) These wrappers dispatch to a non-blocking version of the required system call and notify the calling thread once it is complete. This allows other threads to run in the meantime.

Question: Does thread-safety matter when running threads in Pth? Why or why not? Have a read of the manual page for ctime(3) for some tips (especially the NOTES section).

We start off with our main() function:

int main(void)
{
    /* must be called first: sets up system threads and turns us into the
     * main control thread
     */
    pth_init();

    /* spawn some new threads */
    for (int i = 0; i < 5; i++) {
        pth_spawn(PTH_ATTR_DEFAULT, thread_func, NULL);
    }

    /* sleep the main thread for 15 seconds... this allows the other threads
     * to continue running
     */
    pth_sleep(15);
}

As you can see, we’ve left out the thread_func function: this is a task for you to create (see the next section).

The main function above introduces you to a few of the function calls that exist in the Pth library:

  • pth_init() — initialises the library and sets up the process ready for threading
  • pth_spawn() — similar to pthread_create(), this function is used to create a new thread that will be run
  • pth_sleep() — the Pth-safe version of sleep()

Question: What does the PTH_ATTR_DEFAULT definition mean when given as the first argument? What would you call if you wanted a non-default attribute?

The thread function

Your task is to write the thread_func function which will have the following prototype:

static void *thread_func(void *arg)

(the arg argument is unused in this example). Each thread should busy-wait for 1 second, print out "yielding now", and then yield (see the pth_yield() function). This process should be repeated five times, after which the thread should exit. The time(2) system call would be useful for this.

Observe the behaviour of the test program with top(1) and ps(1) (to get top to display individual threads, type H in top). Think about how many processes are running, how long they run for, how the threads behave, etc.


Linking and running the test program

Some extra compile and linker flags need to be provided when compiling and linking the test program (and any program that uses Pth). You can download a sample Makefile here (make sure you rename it to Makefile).

For example, the extra flags that need to be given are:

PTH_CONFIG=pth-2.0.7-preempt/pth-config

CC=gcc
CFLAGS=-Wall -Wextra -g -std=gnu99 $(shell $(PTH_CONFIG) --cflags)
LDFLAGS=$(shell $(PTH_CONFIG) --ldflags) -Wl,-rpath,$(shell $(PTH_CONFIG) --libdir)
LIBS=$(shell $(PTH_CONFIG) --libs)

where PTH_CONFIG is the path to the pth-config program.

Question: What does the -Wl,-rpath,... argument to the linker do?

Debugging output

Pth allows debugging output to be printed to the terminal while a program linked against it is running. This is very useful and is an invaluable resource during testing, to see what the Pth library is doing.

Enabling this debugging output requires rebuilding the Pth source with debugging enabled, which can be achieved by running the build-pth.sh shell script with the --debug argument.


Exploring Pth

Write a test program that spawns some ‘greedy’ threads — threads that consume a lot of CPU time without yielding — and one or two ‘lean’ threads that yield often. Observe the output of the Pth debugging and try to get a feel for how long each thread is spending executing before yielding.

Pth vs Pthreads

You should see by now that there are limitations of running a threading library in user-space without kernel support. Recreate the test program created above but this time use Pthreads. Remember that since Pthreads has kernel support, threads may call blocking system calls. Run them both at once and compare the differences in behaviour in top(1) and ps(1). How many processes does the Pthreads version use? What is the CPU usage like?

Signals

What happens when you send a signal to an individual thread, running with pthreads? Try kill -SIGHUP pid (where pid is one of the running thread’s process id’s). Did you expect this behaviour? Now try again with Pth.