What is threading?
Consider a function that contains an infinite loop:
void* a(void* data)
{
for ( ; ; ) {
printf("a");
}
return NULL;
}
Calling the function
a();
would cause the program to print "a" forever.
Now consider another function with an infinite loop:
void* b(void* data)
{
for ( ; ; ) {
printf("b");
}
return NULL;
}
Calling the function
b();
, it would cause the program to print "b" forever.
What if we were to consecutively call
a(); b();
? Well, since
a()
contains an infinite loop, the call to
b()
is never going to happen, so all we're going to get is "aaaaaaaaaaaaaaaa". Similarly, if we were to call
b(); a();
all we're going to get is "bbbbbbbbbbbbbbbbb". Is there a way to make the program print a little bit of "a" and a little bit of "b" (i.e. "aaaabbbbbbbaaabbbb") without changing the code (i.e. with infinite loops)? This is what threads are about. By creating threads, we make it possible for multiple infinite loops to execute concurrently. Note that there is a slight difference between
concurrency and
parallelism. Parallel execution means two tasks are running completely simultaneously. Concurrent execution means two tasks are switching turns: task 1 is executing, then task 2, then task 1 again... If context switching is happening fast enough (the OS scheduler is the one deciding this), it would appear as if though they were running in parallel. Most synchronization techniques work the same for both so we'll make no distinction.
By default, every program is single-threaded and the only thread running is the one executing the
main()
function. We can create more threads by calling
pthread_create()
and passing the pointer to a callback function (
void* (*cb)(void*);
). If we create two threads - one for function
a()
and one for function
b()
- we will end up with three threads:
Thread executing a()
Thread executing b()
Thread executing main()
Once threads are created they are marked as , the scheduler will grant them the opportunity to execute at some point. The
main()
thread, on the other hand, is just going to continue its execution as normal. If the main thread encounters
return 0;
the operating system is going to kill the program along with all the threads started. To prevent this from happening, we can use
pthread_join()
to tell the main thread to wait for other threads to finish. Since other threads contain infinite loops, they are never going to finish. The to thread joining was putting an infinite loop in the main function just after thread creation. Most pthread functions return 0 upon success and a value smaller than 0 upon error. We can use
assert()
as a rudimentary runtime error check.
The complete code:
// main.c
#include <stdio.h>
#include <pthread.h>
#include <assert.h>
void* a(void* data)
{
for ( ; ; ) {
printf("a");
}
return NULL;
}
void* b(void* data)
{
for ( ; ; ) {
printf("b");
}
return NULL;
}
int main()
{
pthread_t ta, tb;
assert(pthread_create(&ta, NULL, a, NULL) == 0);
assert(pthread_create(&tb, NULL, b, NULL) == 0);
assert(pthread_join(ta, NULL) == 0);
assert(pthread_join(tb, NULL) == 0);
return 0;
}
Compiling the code above with
gcc -pthread main.c
and running it with
./a.out
should print "aaaabbbbbaaaabbbbb". Since the program will never terminate, it can be interrupted with
Ctrl+C
which is going to send
SIGINT
to the program which, by default, will make it terminate.