0% found this document useful (0 votes)
5 views20 pages

Understanding Thread API in C Programming

Chapter 27 discusses the Thread API, explaining that threads are lightweight processes within a single process that share resources. It covers multithreading in C using the POSIX Threads library, including thread creation, synchronization with pthread_join, and the use of mutexes for critical sections. Additionally, it introduces condition variables for signaling between threads and provides examples of their implementation and usage.

Uploaded by

Kiet Do
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
5 views20 pages

Understanding Thread API in C Programming

Chapter 27 discusses the Thread API, explaining that threads are lightweight processes within a single process that share resources. It covers multithreading in C using the POSIX Threads library, including thread creation, synchronization with pthread_join, and the use of mutexes for critical sections. Additionally, it introduces condition variables for signaling between threads and provides examples of their implementation and usage.

Uploaded by

Kiet Do
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Chapter 27.

Interlude: Thread API


Operating System: Three Easy Pieces
Thread
 A thread is a single sequence stream within a process.
 Single process can have multiple threads, but each thread belongs to exactly one process.
 Threads are also called lightweight processes as they possess some properties of processes.
 Threads are not independent from each other unlike processes. All threads belonging to the same
process share - code section, data section, and OS resources (e.g. open files and signals)
 Each thread has its own (thread control block) - thread ID, program counter (PC), register set, and a stack
Multithreading in C
 Multithreading is a programming technique where a process is divided into multiple
smaller units called threads, which can run simultaneously.

 Threads can be effective only if the CPU is more than 1 otherwise two threads have to
context switch for that single CPU.

 In C programming language, we use the POSIX Threads (pthreads) library to implement


multithreading.

 The POSIX thread libraries are a C/C++ thread API based on standards.

 The pthread library is defined inside <pthread.h> header file

#include <pthread.h>
Thread Creation
 pthread_create(): initializes and starts the thread to run the given function.
#include <pthread.h>
int pthread_create( pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg);

 thread: Pointer to a pthread_t variable where the system stores the ID of the new thread.

 attr: Pointer to a thread attributes object that defines thread properties. Use NULL for default
attributes, such as stack size, scheduling priority, …

 start_routine: a function pointer to the function that the thread will execute.

 arg: A single argument passed to the thread function. Use NULL if no argument is needed. You
can pass a struct or pointer to pass multiple values.

 a void pointer allows us to pass in any type of argument.


start_routine has a single
Thread Creation (Cont.) argument of type void * and
returns a value of type void *

int pthread_create(…, // first two args are the same


void* (*start_routine)(void*),
void* arg);

 If start_routine requires another type argument, the declaration would look like this:
 An integer argument:

int pthread_create(…, // first two args are the same


void* (*start_routine)(int),
int arg);

 Return an integer:

int pthread_create(…, // first two args are the same


int (*start_routine)(void*),
void* arg);
Example: Creating a Thread
#include <pthread.h>

typedef struct __myarg_t {


int a;
int b;
} myarg_t; cast the argument to the type it expects
void *mythread(void *arg) {
myarg_t *m = (myarg_t *) arg;
printf(“%d %d\n”, m->a, m->b);
return NULL;
}
int main(int argc, char *argv[]) {
pthread_t thread1; //Create a pthread_t variable to store thread ID
myarg_t args = {10, 20};
int rc = pthread_create(&thread1, NULL, mythread, &args); // Creating a new thread.
. . .
return 0;
}
// Wait for thread to finish
Wait for a Thread to Complete pthread_join(p, NULL);

int main(int argc, char *argv[]) {


pthread_t thread1; //Create a pthread_t variable to store thread ID
myarg_t args = {10, 20};
int rc = pthread_create(&thread1, NULL, mythread, &args); // Creating a new thread.
return 0;
}

 Wait for the execution of the particular thread: pthread_join()


 main thread may end before the execution of the thread1 and may lead to
unexpected behavior of the program.
 pthread_join() function allows one thread to wait for the termination of another
thread. It is used to synchronize the execution of threads.
Wait for a Thread to Complete (Cont.)
int pthread_join(pthread_t thread, void **value_ptr);

pthread_join() :
 allows one thread to wait for the termination of another thread

 used to synchronize the execution of threads

 Two arguments:
 thread: Specify which thread to wait for

 value_ptr: A pointer to the return value expected to get back

 This is optional and can be set to NULL if you do not need the return value of the thread.

 pthread_join() routine may change the return values. A pointer to that value is required.
#include <stdio.h>
#include <pthread.h> Example: Waiting for Thread Completion
#include <assert.h>
#include <stdlib.h> int main(int argc, char *argv[]) {
int rc;
typedef struct __myarg_t {
int a; pthread_t thread1;
int b; myret_t *m;
} myarg_t; myarg_t args = {10, 20};
typedef struct __myret_t { pthread_create(&thread1, NULL, mythread, &args);
int x; pthread_join(thread1, (void **) &m);
int y; printf(“returned %d %d %d\n”, m->x, m->y, m->z);
int z; return 0;
} myret_t; }
void *mythread(void *arg) {
 thread1 has multiple arguments
myarg_t *m = (myarg_t *) arg;
 packaged into a struct
printf(“%d %d\n”, m->a, m->b);
 casted to the type it expects
myret_t *r = malloc(sizeof(myret_t));
r->x = 1;  thread1 has multiple return values
r->y = 2;  packaged into a struct
r->z = 3;  pthread_join():
return (void *) r;  Thread1 is finished running, the main thread then prints and returns.
}
Example: Simpler Argument Passing to a Thread
 Just passing in or return a single value, we don’t have to package arguments and
return values inside of structures.

void *mythread(void *arg) {


int m = (int) arg;
printf(“%d\n”, m);
return (void *) (arg + 1);
}
int main(int argc, char *argv[]) {
pthread_t thread1;
int rc, m;
pthread_create(& thread1, NULL, mythread, (void *) 100);
pthread_join(thread1, (void **) &m);
printf(“returned %d\n”, m);
return 0;
}
Example: Dangerous Code
 Be careful with how values are returned from a thread.

1 void *mythread(void *arg) {


2 myarg_t *m = (myarg_t *) arg;
3 printf(“%d %d\n”, m->a, m->b);
4 myret_t r; // ALLOCATED ON STACK: BAD!
5 r.x = 1;
6 r.y = 2;
7 return (void *) &r;
8 }

 When the variable r returns, it is automatically de-allocated.

Never return a pointer which refers to something


allocated on the thread’s call stack.
Locks: Defining Critical Sections
 Provide mutual exclusion to a critical section
 Interface int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);

 Usage (w/o lock initialization and error check)

pthread_mutex_t lock;
pthread_mutex_lock(&lock);
x = x + 1; // or whatever your critical section is
pthread_mutex_unlock(&lock);

 No other thread holds the lock → the thread will acquire the lock and enter the critical section.

 If another thread hold the lock → the thread will not return from the call until it has acquired
the lock.

 Returned value If successful, returns 0. If unsuccessful, returns -1.


Locks: Lock Initialization
 All locks must be properly initialized.
 One way: using PTHREAD_MUTEX_INITIALIZER

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;

 The dynamic way: using pthread_mutex_init()

int pthread_mutex_init(pthread_mutex_t *restrict mutex,


const pthread_mutexattr_t *restrict attr)

o Initializes the mutex referenced by mutex with attributes specified by attr.


o If attr is NULL, the default mutex attribute (NONRECURSIVE) is used
o If successful, pthread_mutex_init() returns 0, and the state of the mutex becomes
initialized and unlocked.
o If unsuccessful, pthread_mutex_init() returns -1.
Locks: Unlock & Destroy
 The mutex can be unlocked and destroyed by calling following two functions :
 int pthread_mutex_unlock(pthread_mutex_t *mutex)
o Release a mutex object.
o If one or more threads are waiting to lock the mutex, pthread_mutex_unlock() causes one
of those threads to return from pthread_mutex_lock() with the mutex object acquired.
o If no threads are waiting for the mutex, the mutex unlocks with no current owner.
o Returned value: If successful, returns 0. If unsuccessful, returns -1.

 int pthread_mutex_destroy(pthread_mutex_t *mutex)


o Destroy a mutex object and mutex is set to an invalid value
o The mutex object can be reinitialized using pthread_mutex_init()
o Returned value: If successful, returns 0. If unsuccessful, returns -1.
Locks: Error Check
 Mutex routines may fail also. The failure may allow multiple threads into a
critical section if the errors are not properly checked!

 Check the return codes: int rc = pthread_mutex_init(&lock, NULL);


assert(rc == 0); // always check success!
❖ Lock initialization error check
❖ Check errors code when calling lock and unlock int rc = pthread_mutex_lock(mutex);
assert(rc == 0);

// An example wrapper:
// Use this to keep your code clean but check for failures
void Pthread_mutex_lock(pthread_mutex_t *mutex) {
int rc = pthread_mutex_lock(mutex);
assert(rc == 0);
}
Condition Variables
 Condition variables are useful when some signaling must take place between threads.
 One thread waits on the condition while other threads signal when complete the condition.

 Example:
 A parent thread might wish to check whether a child thread has completed, i.e., join()

 Condition variable: queue of waiting threads


 Waiting on the condition
 An explicit queue that threads can put themselves on when some state of execution is not as desired.

 Signaling on the condition


 Some other thread, when it changes said state, can wake one or more of those waiting threads and allow
them to continue.
Condition Variables - Definition and Routines
 Declare condition variable Pthread_cond_t c;

 Proper initialization is required.


Pthread_cond_t c = PTHREAD_COND_INITIALZER;

 Two operations: wait() and signal()


pthread_cond_wait(pthread_cond_t *c, pthread_mutex_t *m); // wait()

pthread_cond_signal(pthread_cond_t *c); // signal()

 wait()
 Used to put the calling thread to sleep on a condition
 The wait() call takes a mutex as a parameter.
o assumes the lock is held when wait() is called
o releases the lock + puts caller to sleep (atomically)
o when awoken, reacquires lock before returning to the caller.

 signal()
 wake a single sleeping thread waiting on the condition (if >= 1 thread is waiting)
 if there is no waiting thread, just return, doing not
Thread wait/signal Routine
 Declare and initialize condition variable, lock, and a state variable
int ready = 0;
pthread_mutex_t m = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t c = PTHREAD_COND_INITIALIZER;
 A thread calling wait() routine:
 Check the state variable, if not ready, call wait()
 The wait call releases the lock when putting said caller to sleep.
 Before returning after being woken, the wait call re-acquire the lock.
 A thread calling signal() routine:
 Set the state variable and to wake signal the waiting thread
 Use a lock to ensure no race between interacting with state and wait/signal

void thread_wait() { void thread_signal() {


Pthread_mutex_lock(&m); Pthread_mutex_lock(&m);
while (ready == 0) ready = 1;
Pthread_cond_wait(&c, &m); Pthread_cond_signal(&c);
Pthread_mutex_unlock(&m); Pthread_mutex_unlock(&m);
} }
Thread wait/signal Routine (Cont.)
 The waiting thread re-checks the condition in a while loop, instead of a simple if statement.

void thread_wait() {
Pthread_mutex_lock(&m);
while (ready == 0)
Pthread_cond_wait(&c, &m);
Pthread_mutex_unlock(&m);
}

 Some pthread implementations could spuriously wake up a waiting thread

 Without rechecking, the waiting thread will continue thinking that the condition has
changed even though it has not.
Compiling and Running
 To compile the previous example programs, you must include the header pthread.h
 Explicitly link with the pthreads library, by adding the –pthread flag.

prompt> gcc –o main main.c –Wall -pthread

 For more information,

man –k pthread

You might also like