1. Thread Creation
-
if man pthread_create does not work:
sudo apt install -y manpages-posix manpages-posix-dev
pthread_create(), pthread_join()
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg); int pthread_join(pthread_t thread, void **retval);
threads-api/thread_create.c1 #include <assert.h> 2 #include <stdio.h> 3 #include <pthread.h> 4 5 typedef struct { 6 int a; 7 int b; 8 } myarg_t; 9 10 void *mythread(void *arg) { 11 myarg_t *args = (myarg_t *) arg; 12 printf("%d %d\n", args->a, args->b); 13 return NULL; 14 } 15 16 int main(int argc, char *argv[]) { 17 pthread_t p; 18 myarg_t args = { 10, 20 }; 19 20 int rc = pthread_create(&p, NULL, mythread, &args); 21 assert(rc == 0); 22 (void) pthread_join(p, NULL); 23 printf("done\n"); 24 return 0; 25 }
$ gcc -Wall -o thread_create thread_create.c -pthread $ ./thread_create 10 20 done $
2. Simple Args
-
threads-api/thread_create_simple_args.c
1 #include <stdio.h> 2 #include <pthread.h> 3 #include "common_threads.h" 4 5 void *mythread(void *arg) { 6 long long int value = (long long int) arg; 7 printf("%lld\n", value); 8 return (void *) (value + 1); 9 } 10 11 int main(int argc, char *argv[]) { 12 pthread_t p; 13 long long int rvalue; 14 Pthread_create(&p, NULL, mythread, (void *) 100); 15 Pthread_join(p, (void **) &rvalue); 16 printf("returned %lld\n", rvalue); 17 return 0; 18 }
$ gcc -Wall -I../include -o thread_create_simple_args \ thread_create_simple_args.c -pthread $ ./thread_create_simple_args 100 returned 101 $
3. Return Args
-
threads-api/thread_create_with_return_args.c
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <pthread.h> 4 #include "common_threads.h" 5 6 typedef struct { 7 int a; 8 int b; 9 } myarg_t; 10 11 typedef struct { 12 int x; 13 int y; 14 } myret_t; 15 16 void *mythread(void *arg) { 17 myarg_t *args = (myarg_t *) arg; 18 printf("args %d %d\n", args->a, args->b); 19 myret_t *rvals = malloc(sizeof(myret_t)); 20 assert(rvals != NULL); 21 rvals->x = 1; 22 rvals->y = 2; 23 return (void *) rvals; 24 } 25 26 int main(int argc, char *argv[]) { 27 pthread_t p; 28 myret_t *rvals; 29 myarg_t args = { 10, 20 }; 30 Pthread_create(&p, NULL, mythread, &args); 31 Pthread_join(p, (void **) &rvals); 32 printf("returned %d %d\n", rvals->x, rvals->y); 33 free(rvals); 34 return 0; 35 }
// bad alternative: void *mythread(void *arg) { myarg_t *args = (myarg_t *) arg; printf("args %d %d\n", args->a, args->b); myret_t oops = { 1, 2}; return &oops; // mistake, local variable deallocated on return } $ gcc -Wall -I../include -o thread_create_with_return_args \ thread_create_with_return_args.c -pthread $ ./thread_create_with_return_args args 10 20 returned 1 2 $
4. Locks
-
pthread_mutex_init(),
pthread_mutex_lock() (and unlock)
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int pthread_mutex_lock(pthread_mutex_t *mutex); int pthread_mutex_unlock(pthread_mutex_t *mutex);
Example:pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; // or: pthread_mutex_t lock; assert( pthread_mutex_init(&lock,NULL) == 0); ... pthread_mutex_lock(&lock); x = x + 1; // or whatever your critical section is ... pthread_mutex_unlock(&lock);
5. Conditions
-
pthread_cond_init(),
pthread_cond_signal(),
pthread_cond_wait()
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
Example:pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER; pthread_cond_t cond = PTHREAD_COND_INITIALIZER; // some thread, waiting: Pthread_mutex_lock(&lock); while (ready == 0) Pthread_cond_wait(&cond, &lock); // condition occurred and we have the lock here ... do stuff ... Pthread_mutex_unlock(&lock); // some other thread, signaling: Pthread_mutex_lock(&lock); ... do stuff ... ready = 1; Pthread_cond_signal(&cond); Pthread_mutex_unlock(&lock);
6. Exercises
-
See the book for exercises using helgrind,
i.e.
valgrind --tool=helgrind
Example: Q1,2 main-race.c
1 #include <stdio.h> 2 3 #include "common_threads.h" 4 5 int balance = 0; 6 7 void* worker(void* arg) { 8 balance++; // unprotected access 9 return NULL; 10 } 11 12 int main(int argc, char *argv[]) { 13 pthread_t p; 14 Pthread_create(&p, NULL, worker, NULL); 15 balance++; // unprotected access 16 Pthread_join(p, NULL); 17 return 0; 18 } $ valgrind --tool=helgrind ./main-race ... ==25147== Possible data race during read of size 4 at 0x30A040 by thread #1 ==25147== Locks held: none ... ==25147== This conflicts with a previous write of size 4 by thread #2 ==25147== Locks held: none ...
Fix using a lock.
7. Spinning vs. Yielding
-
Example: Q6,7 main-signal.c
1 #include <stdio.h> 2 3 #include "common_threads.h" 4 5 int done = 0; 6 7 void* worker(void* arg) { 8 printf("this should print first\n"); 9 done = 1; 10 return NULL; 11 } 12 13 int main(int argc, char *argv[]) { 14 pthread_t p; 15 Pthread_create(&p, NULL, worker, NULL); 16 while (done == 0) 17 ; 18 printf("this should print last\n"); 19 return 0; 20 }
valgrind warns about a possible data race, but the warning is spurious and the code is correct.Improve using sched_yield()