In this section, I present some utility code I use to make multithreading in C a little more pleasant. The examples in Section [csync] are based on this code.
Probably the most popular threading standard used with C is POSIX Threads, or Pthreads for short. The POSIX standard defines a thread model and an interface for creating and controlling threads. Most versions of UNIX provide an implementation of Pthreads.
Using Pthreads is like using most C libraries:
You include headers files at the beginning of your program.
You write code that calls functions defined by Pthreads.
When you compile the program, you link it with the Pthread library.
For my examples, I include the following headers:
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <semaphore.h>
The first two are standard; the third is for Pthreads and the fourth is for semaphores. To compile with the Pthread library in gcc, you can use the -l option on the command line:
gcc -g -O2 -o array array.c -lpthread
This compiles array.c with debugging info and optimization, links with the Pthread library, and generates an executable named array.
If you are used to a language like Python that provides exception handling, you will probably be annoyed with languages like C that require you to check for error conditions explicitly. I often mitigate this hassle by wrapping library function calls together with their error-checking code inside my own functions. For example, here is a version of malloc that checks the return value.
void *check_malloc(int size)
{
void *p = malloc (size);
if (p == NULL) {
perror ("malloc failed");
exit (-1);
}
return p;
}
I’ve done the same thing with the Pthread functions I’m going to use; here’s my wrapper for pthread_create.
pthread_t make_thread(void *(*entry)(void *), Shared *shared)
{
int n;
pthread_t thread;
n = pthread_create (&thread, NULL, entry, (void *)shared);
if (n != 0) {
perror ("pthread_create failed");
exit (-1);
}
return thread;
}
The return type from pthread_create is pthread_t, which you can think of as a handle for the new thread. You shouldn’t have to worry about the implementation of pthread_t, but you do have to know that it has the semantics of a primitive type13. That means that you can think of a thread handle as an immutable value, so you can copy it or pass it by value without causing problems. I point this out now because it is not true for semaphores, which I will get to in a minute.
If pthread_create succeeds, it returns 0 and my function returns the handle of the new thread. If an error occurs, pthread_create returns an error code and my function prints an error message and exits.
The parameters of pthread_create take some explaining. Starting with the second, Shared is a user-defined structure that contains shared variables. The following typedef statement creates the new type:
typedef struct {
int counter;
} Shared;
In this case, the only shared variable is counter. make_shared allocates space for a Shared structure and initializes the contents:
Shared *make_shared ()
{
int i;
Shared *shared = check_malloc (sizeof (Shared));
shared->counter = 0;
return shared;
}
Now that we have a shared data structure, let’s get back to pthread_create. The first parameter is a pointer to a function that takes a void pointer and returns a void pointer. If the syntax for declaring this type makes your eyes bleed, you are not alone. Anyway, the purpose of this parameter is to specify the function where the execution of the new thread will begin. By convention, this function is named entry:
void *entry (void *arg)
{
Shared *shared = (Shared *) arg;
child_code (shared);
pthread_exit (NULL);
}
The parameter of entry has to be declared as a void pointer, but in this program we know that it is really a pointer to a Shared structure, so we can typecast it accordingly and then pass it along to child_code, which does the real work.
When child_code returns, we invoke pthread_exit which can be used to pass a value to any thread (usually the parent) that joins with this thread. In this case, the child has nothing to say, so we pass NULL.
When one thread want to wait for another thread to complete, it invokes pthread_join. Here is my wrapper for pthread_join:
void join_thread (pthread_t thread)
{
int ret = pthread_join (thread, NULL);
if (ret == -1) {
perror ("pthread_join failed");
exit (-1);
}
}
The parameter is the handle of the thread you want to wait for. All my function does is call pthread_join and check the result.
The POSIX standard specifies an interface for semaphores. This interface is not part of Pthreads, but most UNIXes that implement Pthreads also provide semaphores. If you find yourself with Pthreads and without semaphores, you can make your own; see Section [makeyourown].
POSIX semaphores have type sem_t. You shouldn’t have to know about the implementation of this type, but you do have to know that it has structure semantics, which means that if you assign it to a variable you are making a copy of the contents of a structure. Copying a semaphore is almost certainly a bad idea. In POSIX, the behavior of the copy is undefined.
In my programs, I use capital letters to denote types with structure semantics, and I always manipulate them with pointers. Fortunately, it is easy to put a wrapper around sem_t to make it behave like a proper object. Here is the typedef and the wrapper that creates and initializes semaphores:
typedef sem_t Semaphore;
Semaphore *make_semaphore (int n)
{
Semaphore *sem = check_malloc (sizeof(Semaphore));
int ret = sem_init(sem, 0, n);
if (ret == -1) {
perror ("sem_init failed");
exit (-1);
}
return sem;
}
make_semaphore takes the initial value of the semaphore as a parameter. It allocates space for a Semaphore, initializes it, and returns a pointer to Semaphore.
sem_init uses old-style UNIX error reporting, which means that it returns -1 if something went wrong. Once nice thing about these wrapper functions is that we don’t have to remember which functions use which reporting style.
With these definitions, we can write C code that almost looks like a real programming language:
Semaphore *mutex = make_semaphore(1);
sem_wait(mutex);
sem_post(mutex);
Annoyingly, POSIX semaphores use post instead of signal, but we can fix that:
int sem_signal(Semaphore *sem)
{
return sem_post(sem);
}
That’s enough cleanup for now.
The primary difference is that I sometimes use indentation to indicate code that is protected by a mutex, which would cause syntax errors in Python.↩
Actually, V and P aren’t completely meaningless to people who speak Dutch.↩
Although this metaphor is helpful, for now, it can also be misleading, as you will see in Section [water]↩
Thanks to Matt Tesch for this solution!↩
Song lyric performed by Shania Twain↩
A semaphore used as a queue is very similar to a condition variable. The primary difference is that threads have to release the mutex explicitly before waiting, and reacquire it explicitly afterwards (but only if they need it).↩
This problem is based on a cartoonish representation of the history of Western missionaries among hunter-gatherer societies. Some humor is intended by the allusion to the Dining Philosophers problem, but the representation of “savages” here isn’t intended to be any more realistic than the previous representation of philosophers. If you are interested in hunter-gatherer societies, I recommend Jared Diamond’s Guns, Germs and Steel, Napoleon Chagnon’s The Yanomamo, and Redmond O’Hanlon’s In Trouble Again, but not Tierney’s Darkness in El Dorado, which I believe is unreliable.↩
The plural of rendezvous is rare, and not all dictionaries agree about what it is. Another possibility is that the plural is also spelled “rendezvous,” but the final “s” is pronounced.↩
Later I learned that a nearly identical problem appears in Andrews’s Concurrent Programming↩
Modus Hall is one of several nicknames for the modular buildings, aka Mods, that some students lived in while the second residence hall was being built.↩
Well, almost. It turns out that a well-timed spurious wakeup (see http://en.wikipedia.org/wiki/Spurious_wakeup) can violate this guarantee.↩
At the time of this writing, this bug had been reported and assigned number 1167930, but it was open and unassigned (https://sourceforge.net/projects/python/).↩
Like an integer, for example, which is what a pthread_t is in all the implementations I know.↩
This website is provided under a CC BY-SA license by the
The Berea CS Department.
Fall 2013 offering of taught by Matt Jadud