1. Goals for these weeks:

  • Learn how to write a pthread program

  • Learn some Learn some Unix commands for finding out information about CPUs and threads running in the system

  • Practice with some debugging techniques for pthread programs

2. Starting Point Code

Start by creating a week12 directory in your cs31/weeklylabs subdirectory and copying over some files:

cd ~/cs31/weeklylabs
mkdir week12
cd week12
pwd
/home/you/cs31/weeklylabs/week12
cp ~adanner/public/cs31/week12/* ./
ls

3. Small pthread programs

The files first.c, second.c, and third.c provide examples of small pthread programs. In first.c, the program creates two threads using pthread_create, then waits for each to finish using pthread_join. The main function hello for each thread prints out a message then returns. Note the basic prototype for the thread main function. You must use the void* return type and the void *arg parameter list, even if your thread main function does not need input or return a value.

In second.c, we show how to pass a thread ID generated by the program to each thread. This program also shows that the pthread library also generates a pthread_self() ID for each thread. This ID is used for pthread_join, but the other local thread ID (0/1) is often more helpful for dividing work amongst threads.

In third.c, we expand the input argument to a struct, and show how to dynamically create array of structs. This program determines the number of threads as a command line argument and divides "work" across all the threads. In this small example, the work is specified by a total amount of time on the command line, which is partitioned evenly across threads. If you run

time ./third 5 1
time ./third 5 5

You should see that total time is faster with five threads, as they are able to run concurrently. Keep in mind that the threads don’t really need to use CPU resources while sleeping, so the performance gains are a bit exaggerated. Would you expect a program to run 100 times faster with 100 threads on a machine with only 8 cores?

time ./third 30 100

4. Example pthread program with a race condition

Let’s look at the hello.c program together. It show an example of a simple pthread program that:

  1. spawns some worker threads by calling pthread_create

  2. passes parameter values (thread logical identifier values) to each thread’s main function via its void *args parameter.

    This shows an example of how the thread main loop function can get is argument value(s) from it parameter

  3. calls to pthread_join to wait for all spawned threads to exit.

Let’s try running it a few times with different numbers of threads.

You can also review some pthread pdf slides on the hello.c example.

This program is an example of a multi-threaded program with a race condition. Let’s try to fix it using some pthread synchronization primitives. But first, lets look at the following program that has some examples of using pthread synchronization primitives…​

5. Example pthread program that uses synchronization primitives

The synch.c contains some examples using pthreads mutex and barrier synchronization. It also shows an example of defining a struct type that can be used to pass several values to a threads main function via its single void *args parameter. Try running this to see understand what it is doing. Then go back and fix the hello.c program by adding in some synchronization to remove the race condition.

6. top, htop, and threads

top and htop are Unix utilities that list information about processes and threads and how they are using resources like memory and CPU. If you run top with no command line options, then it displays per-process statistics. If you run top with -H, top will display statistics for individual threads (if you run the synch program for a large number of threads, you can see them show up in top):

top -H

Let’s try out the example of configuring top from this page: using top and htop to change what top displays. Then we can try running a multi-threaded process and see what top shows us.

Let’s run the synch program with a bunch of threads, and then top -H in another window to see what we can see.

We can also try running htop.

We can also see the number of CPU cores on a particular machine, and a lot of information about each one, by looking at a file in the /proc file system:

cat /proc/cpuinfo | more

(| more pipes cat output through the more reader that allows you to scroll through a page at a time using the space bar, enter q to quit):

7. debugging pthread programs

Debugging threaded programs can be tricky because there are multiple streams of execution. In general, try to debug with as few threads as possible, and if you use printfs, print out a thread id and call fflush after. You can also put printf’s in conditional statements to only have one of the threads print out information (or only some of the threads, or only some of the information, …​). For example, if each thread is passed a logical thread id value on start-up, and stores its value in a local variable named my_tid then you could have logical thread 1 be the debug output printing thread to do something like:

if(my_tid == 1) {
  printf("Tid:%d: value of count is now %d my i is %d\n",
      my_tid,count,i);
  fflush(stdout);
}

7.1. gdb and pthreads

gdb has support for debugging multi-threaded processes. If you want to try using gdb to debug your pthread code, here is some general information about it and an example you can try out: Debugging pthreads programs with gdb. It contains an example run of debugging the racecond program you copied over with this week’s in-class code.

More detailed information about gdb and pthreads can be found:

8. Handy References