Threading - Learning C by Example (2015)

Learning C by Example (2015)

8. Threading

This chapter explains how to work with threading using C.

8.1 Creating Thread

A thread of execution is the smallest unit of processing that a scheduler works on. A process can have multiple threads of execution which are executed asynchronously.

On Linux/Unix, we can create a thread using pthread_create() that can be defined as follows.

#include <pthread.h>

int pthread_create (pthread_t *thread,

const pthread_attr_t *attr,

void *(*start_routine) (void *),

void *arg);

Note:

· pthread_attr_t is thread attributes

· start_routine is a function that will be executed

· arg is argument that passes to function

For illustration, we create a thread by creating a file, called createthread.c. Write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

void* perform(void *arg)

{

int i;

int *n = (int *)arg;

printf("processing from thread\r\n");

for(i=0;i<(*n);i++)

{

printf("%d ",i);

}

printf("\r\n");

}

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

{

pthread_t thread;

int ret;

errno = 0;

int n = 10;

ret = pthread_create (&thread, NULL, perform, &n);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

int c = getchar(); // hold app to exit

return 0;

}

You can see we pass parameter n=10 to function perform(). On function perform(), we just do looping.

Now save this code. To compile and link, you can type the following command.

$ gcc -pthread -o createthread createthread.c

If success, you can run this app.

$ ./createthread

The following is sample output.

ch8-1

8.2 Thread ID

We can obtain the running thread using pthread_self() and following is its syntax.

#include <pthread.h>

pthread_t pthread_self (void);

To use this function, you can call pthread_self() inside thread function.

For illustration, create file, called threadid.c, and write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

void* perform(void *arg)

{

int i;

int *n = (int *)arg;

pthread_t tid;

printf("processing from thread\r\n");

/* get the calling thread's ID */

tid = pthread_self();

printf("Thread id: %d \r\n",(int)tid);

for(i=0;i<(*n);i++)

{

printf("%d ",i);

}

printf("\r\n");

}

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

{

pthread_t thread;

int ret;

errno = 0;

int n = 10;

ret = pthread_create (&thread, NULL, perform, &n);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

int c = getchar(); // hold app to exit

return 0;

}

Save this code. Then try to compile and run it.

The following is sample output.

ch8-2

8.3 Terminating Thread

We can terminate a thread using two approaches, terminating itself and terminating others. We are going to explore these approaches on next section.

8.3.1 Terminating Itself

A thread can stop its processing using pthread_exit(). The following is a syntax of pthread_exit().

#include <pthread.h>

void pthread_exit (void *retval);

For illustration, we build app based threading and call function perform(). On function perform(), we do looping and will exit from internal thread after looped index 3 by calling pthread_exit().

To implement, we create a file, called selfexit.c, and write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

void* perform(void *arg)

{

int i;

int *n = (int *)arg;

printf("processing from thread\r\n");

for(i=0;i<(*n);i++)

{

if(i==3)

{

printf("Terminating thread\r\n");

/* exit this thread */

pthread_exit((void *)0);

}

printf("%d ",i);

}

printf("\r\n");

}

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

{

pthread_t thread;

int ret;

errno = 0;

int n = 10;

ret = pthread_create (&thread, NULL, perform, &n);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

int c = getchar(); // hold app to exit

return 0;

}

Save this code. Compile and run this code.

The following is a sample output.

ch8-3

8.3.2 Terminating Others

We also can terminate a thread by calling pthread_cancel() with passing thread object.

#include <pthread.h>

int pthread_cancel (pthread_t thread);

A thread can be configured to enable/disable for canceling thread using pthread_setcancelstate(). pthread_testcancel() function creates a cancellation point in the calling thread. pthread_setcancelstate() and pthread_testcancel() functions can be defined as follows

#include <pthread.h>

int pthread_setcancelstate(int state, int *oldstate);

void pthread_testcancel(void);

For illustration, we build app to create a thread and try to terminate it using pthread_cancel().

Create a file, called terminateother.c, and write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

void* perform(void *arg)

{

int n = 0;

// I'm not ready to be canceled

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);

printf("processing from thread\r\n");

while(1)

{

printf("%d ",n);

n++;

pthread_testcancel();

if(n>5)

{

// I'm ready to be canceled

pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

}

sleep(1);

}

printf("\r\n");

}

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

{

pthread_t thread;

int ret, status;

errno = 0;

ret = pthread_create (&thread, NULL, perform, NULL);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

sleep(10);

errno = 0;

status = pthread_cancel(thread);

if (status)

{

printf("\n pthread_cancel() failed with error [%s]\n",strerror(errno));

return -1;

}

int c = getchar(); // hold app to exit

return 0;

}

Explanation:

· On main entry main(), we create a thread with passing perform() function

· We hold current process using sleep() for 10 seconds

· After that, we try to terminate a thread by calling pthread_cancel()

· pthread_cancel() function returns status value. You can verify this return value

· On perform() function, firstly we call pthread_setcancelstate() to disable the thread cancelling

· After looping n>5, we enable the thread cancelling

· pthread_testcancel() function is called to create a cancellation point

Save this code. Compile and run it.

The following is a sample output.

ch8-8

8.4 Joining Thread

Joining thread is one of thread synchnonization to terminate the executing thread. We can use pthread_join() and defined as follows.

#include <pthread.h>

int pthread_join (pthread_t thread, void **retval);

We need to pass thread attribute to enable joining thread. We can use pthread_attr_init(), pthread_attr_setdetachstate(), and pthread_attr_destroy() that can be defined as follows.

#include <pthread.h>

int pthread_attr_init(pthread_attr_t *attr);

int pthread_attr_destroy(pthread_attr_t *attr);

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);

For illustration, we build a simple thread app to compute a simple math equation.

Create a file, called jointhread.c, and write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

#include <math.h>

void* compute(void *arg)

{

int i;

pthread_t tid;

double result = 0;

tid = pthread_self();

printf("processing from thread %ld \r\n",tid);

// do something

for(i=0;i<50;i++)

{

result = result + sin(i/2);

}

printf("Thread %ld. Completed, result=%e \r\n",tid,result);

pthread_exit((void *)0);

}

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

{

pthread_t thread[5];

pthread_attr_t attr;

void *status;

int i, ret;

errno = 0;

// thread attribute

pthread_attr_init(&attr);

pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

for(i=0;i<5;i++)

{

ret = pthread_create (&thread[i], &attr, compute, NULL);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

}

sleep(2);

// joining thread

printf("joining thread\r\n");

for(i=0;i<5;i++)

{

ret = pthread_join(thread[i], &status);

if (ret)

{

printf("\n pthread_join() failed with error [%s]\n",strerror(errno));

return -1;

}

printf("Completed join with thread %ld. Status: %ld\n", i,(int)status);

}

int c = getchar(); // hold app to exit

return 0;

}

Explanation:

· On main() entry point, we initialize thread attribute using pthread_attr_init() and pthread_attr_setdetachstate() to activate joining thread

· We create 5 threads using pthread_create()

· After 2 seconds, we call pthread_join() to wait 5 exiting threads

· On computer() function, we calculate simple math

· If finished, it call pthread_exit()

Save this code. Because we use math.h, we add library -lm on compiling and linking. The following is a syntax to compile this code.

$ gcc -pthread -o jointhread jointhread.c -lm

$ ./jointhread

Now you can run it. The sample output is shown Figure below.

ch8-4

8.5 Thread Mutex

You have resources such as data variable, file, database which can be accessed only by a thread. It's called mutex. For illustration, you have data which is stored on variable n. You also have 5 threads to access this data. The first rule is data variable can be accessed by one thread. It means data variable will be locked so another thread cannot access it.

Mutex is one of thread synchronization mechanism to control accessing resource.

You can implement thread mutex using the following functions.

#include <pthread.h>

int pthread_mutex_init(pthread_mutex_t *restrict mutex,

const pthread_mutexattr_t *restrict attr);

int pthread_mutex_destroy(pthread_mutex_t *mutex);

int pthread_mutex_lock(pthread_mutex_t *mutex);

int pthread_mutex_unlock(pthread_mutex_t *mutex);

Note:

· pthread_mutex_init() initialize mutex object

· pthread_mutex_destroy() destroy mutex object

· pthread_mutex_lock() locak mutex object

· pthread_mutex_lock() unlock/release mutex object

For implementation, we create a file, called mutex.c. Firstly we define headers and global variables. Write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

pthread_t thread[5];

int total_finished_jobs;

pthread_mutex_t lock;

Variable total_finished_jobs can be accessed by only one thread.

On main() point entry, we instantiate mutex object and create 5 threads.

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

{

int i, ret;

errno = 0;

total_finished_jobs = 0;

if (pthread_mutex_init(&lock, NULL) != 0)

{

printf("\n pthread_mutex_init() failed with error [%s]\n",strerror(errno));

return 1;

}

for(i=0;i<5;i++)

{

ret = pthread_create (&thread[i], NULL, perform_job, NULL);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

}

int c = getchar(); // hold app to exit

pthread_mutex_destroy(&lock);

return 0;

}

Each thread calls perform_job() function. When access variable total_finished_jobs, we try to lock object lock. After that, we update a value of total_finished_jobs.

The following is implementation of perform_job() function.

void* perform_job(void *arg)

{

int i;

pthread_t tid;

tid = pthread_self();

printf("processing from thread %ld \r\n",tid);

printf("Thread %ld. Started Job. \r\n",tid);

// do something for doing job

for(i=0;i<20;i++)

{

sleep(1);

}

// update job counter

pthread_mutex_lock(&lock);

total_finished_jobs++;

printf("Thread %ld. Finished Job. \r\n",tid);

printf("Total current finished job: %d \r\n",total_finished_jobs);

pthread_mutex_unlock(&lock);

}

Now save all code. You can compile and run it.

You can see a sample output on the following Figure.

ch8-5

8.6 Condition Variables

We have learned thread synchronization using thread mutex. Now we explore another thread synchronization using condition variables in controlling accessing resource.

There are two methods to implement condition variables on thread synchronization:

· Signaling

· Broadcasting

Each method will be explained on next section.

8.6.1 Signaling

The idea of signaling is after you access a resource you must notify to another thread to start accessing resource. To implement it, we can use the following functions.

#include <pthread.h>

int pthread_cond_init(pthread_cond_t *restrict cond,

const pthread_condattr_t *restrict attr);

int pthread_cond_wait(pthread_cond_t *restrict cond,

pthread_mutex_t *restrict mutex);

int pthread_cond_destroy(pthread_cond_t *cond);

To notify another thread, you can use pthread_cond_signal() which is defined as below.

#include <pthread.h>

int pthread_cond_signal(pthread_cond_t *cond);

For illustration, we create two threads that act as producer and consumer. Producer can write data and consumer can only read data. The data can be accessed by one thread, either producer or consumer.

Let's start to create a file, called condvariable.c. Firstly we define headers and global variables.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

#define BUFF_LEN 30

int buffer[BUFF_LEN];

int index_put=0, index_get=0;

int count = 0;

pthread_cond_t empty, get;

pthread_mutex_t lock;

On main() entry point, we initialize object mutex and condition variables (empty and fill). We also create 2 thread, producer and consumer.

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

{

pthread_t thread_consumer, thread_producer;

int ret;

errno = 0;

int loops = 15;

// initialization

if (pthread_mutex_init(&lock, NULL) != 0)

{

printf("\n pthread_mutex_init() failed with error [%s]\n",strerror(errno));

return 1;

}

if (pthread_cond_init(&empty, NULL) != 0)

{

printf("\n pthread_cond_init() failed with error [%s]\n",strerror(errno));

return 1;

}

if (pthread_cond_init(&get, NULL) != 0)

{

printf("\n pthread_cond_init() failed with error [%s]\n",strerror(errno));

return 1;

}

// create thread

ret = pthread_create (&thread_consumer, NULL, consumer, &loops);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

ret = pthread_create (&thread_producer, NULL, producer, &loops);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

int c = getchar(); // hold app to exit

pthread_mutex_destroy(&lock);

pthread_cond_destroy(&empty);

pthread_cond_destroy(&get);

return 0;

}

On producer() function we do looping until obtain signal empty. After obtained a signal, we add a value on array buffer. If done, it will notify signal get.

void *producer(void *arg)

{

int i;

int *loops = (int *)arg;

for (i = 0; i < (*loops); i++)

{

pthread_mutex_lock(&lock);

while (count == BUFF_LEN)

pthread_cond_wait(&empty, &lock);

buffer[index_put] = i;

index_put = (index_put + 1) % BUFF_LEN;

count++;

pthread_cond_signal(&get);

pthread_mutex_unlock(&lock);

}

printf("exit from producer\r\n");

}

On consumer() function, firstly we wait signal fill. After obtained signal, we read data from array buffer. If done, we notify signal empty.

void *consumer(void *arg)

{

int i;

int *loops = (int *)arg;

for (i = 0; i < (*loops); i++)

{

pthread_mutex_lock(&lock);

while (count == BUFF_LEN)

pthread_cond_wait(&get, &lock);

int tmp = buffer[index_get];

index_get = (index_get + 1) % BUFF_LEN;

count--;

printf("Value: %d\r\n", tmp);

pthread_cond_signal(&empty);

pthread_mutex_unlock(&lock);

}

printf("exit from consumer\r\n");

}

Save this code. Compile and run it.

The following is a sample output.

ch8-6

8.6.2 Broadcasting

We can notify some threads using broadcasting signal. It can use pthread_cond_broadcast() and defined as follows.

#include <pthread.h>

int pthread_cond_broadcast(pthread_cond_t *cond);

What's difference between pthread_cond_broadcast() and pthread_cond_signal()? pthread_cond_broadcast() notifies several threads but pthread_cond_signal() notifies a thread.

For illustration, we use same code on previous section (section 8.6.1). In this scenario, we have 2 consumer threads and 1 producer thread.

To start, we create a file, called broadcast.c. Firstly, we define headers and global variables. Write this code.

#include <stdio.h>

#include <pthread.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

#define BUFF_LEN 30

int buffer[BUFF_LEN];

int index_put=0, index_get=0;

int count = 0;

pthread_cond_t empty, get;

pthread_mutex_t lock;

On main() entry point, we initialize mutex and condition variables. Then we create a producer thread and 2 consumer threads.

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

{

pthread_t thread_consumer[2], thread_producer;

int ret;

errno = 0;

int loops = 15;

// initialization

if (pthread_mutex_init(&lock, NULL) != 0)

{

printf("\n pthread_mutex_init() failed with error [%s]\n",strerror(errno));

return 1;

}

if (pthread_cond_init(&empty, NULL) != 0)

{

printf("\n pthread_cond_init() failed with error [%s]\n",strerror(errno));

return 1;

}

if (pthread_cond_init(&get, NULL) != 0)

{

printf("\n pthread_cond_init() failed with error [%s]\n",strerror(errno));

return 1;

}

// create thread

ret = pthread_create (&thread_consumer[0], NULL, consumer, &loops);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

ret = pthread_create (&thread_consumer[1], NULL, consumer, &loops);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

ret = pthread_create (&thread_producer, NULL, producer, &loops);

if (ret)

{

printf("\n pthread_create() failed with error [%s]\n",strerror(errno));

return -1;

}

int c = getchar(); // hold app to exit

pthread_mutex_destroy(&lock);

pthread_cond_destroy(&empty);

pthread_cond_destroy(&get);

return 0;

}

On producer() function, we insert data after obtained signal empty. Then we call pthread_cond_broadcast() to notify all consumer threads.

void *producer(void *arg)

{

int i;

int *loops = (int *)arg;

for (i = 0; i < (*loops); i++)

{

pthread_mutex_lock(&lock);

while (count == BUFF_LEN)

pthread_cond_wait(&empty, &lock);

buffer[index_put] = i;

index_put = (index_put + 1) % BUFF_LEN;

count++;

pthread_cond_broadcast(&get); //broadcast to consumer

pthread_mutex_unlock(&lock);

}

printf("exit from producer\r\n");

}

On consumer() function, we get data from array after obtained signal get. After that, we notify producer thread using pthread_cond_signal().

void *consumer(void *arg)

{

int i;

int *loops = (int *)arg;

pthread_t tid;

tid = pthread_self();

for (i = 0; i < (*loops); i++)

{

pthread_mutex_lock(&lock);

while (count == BUFF_LEN)

pthread_cond_wait(&get, &lock);

int tmp = buffer[index_get];

index_get = (index_get + 1) % BUFF_LEN;

count--;

printf("Thread consumer id: %ld. Value: %d\r\n", tid, tmp);

pthread_cond_signal(&empty);

pthread_mutex_unlock(&lock);

}

printf("exit from consumer\r\n");

}

Save all code. Now you can compile and run it.

The following is a sample output.

ch8-7