UC3M

Telematic/Audiovisual Syst./Communication Syst. Engineering

Systems Architecture

September 2017 - January 2018

11.2.  Threads

Let's start exploring the creation of threads. In its most basic form, this interface provides the ability to create threads and wait for their execution to end. Threads are created with some input parameters and return a result once the code is complete. It is the same kind of relationship one have a program that has been launched from the terminal to its creator.

The next piece of code shows an example of an application that receives, as an input parameter, a number which it is increased by one unit and returns that result to the thread that invoked, waiting for the result and printing out the result (Note: sleeps have educational purposes and they help us to control the time required to run a thread).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// compile with $ gcc -Wall -g *.c -pthread -o program
// run with ./program
// check with valgrind --tool=helgrind ./program
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void* thread_run(void* data)
{ sleep(2); 
  printf("[TH_1:%ld]: Hello from the thread \n", pthread_self());
  sleep(1);
  (*(int*)data)++;
  printf("[TH_1: %ld]: To exit...............\n",pthread_self());
  pthread_exit(data);
}

int main()
{
  pthread_t thread;
  int data=0;
  int thread_rc;
  printf("[MAIN:%ld]: Starting............ \n",pthread_self());
  if ((thread_rc=pthread_create(&thread,NULL,thread_run,&data))!=0)
  {
    printf("Error creating the thread. Code %i",thread_rc);
    return -1;
  }
  sleep(1);
  printf("[MAIN:%ld]: Thread allocated \n",pthread_self());
  int *ptr_output_data;
  pthread_join(thread,(void **)&ptr_output_data);
  printf("[MAIN:%ld]: Thread returns %d \n",pthread_self(), *ptr_output_data);
  return 0;
} 

The code displays the type of application that can be done with POSIX threads. Let's see some remarkable points of the code:

  • To use the interface for threads, you need to include the pthread.h header. Also, you have to link the compiled code with the -lpthread option.

  • Creating a thread is done with pthread_create. From this point, if the create function does not produce any mistakes, there are two threads of execution: the calling program and a function whose name is passed as parameter and in our case corresponds to thread_run. This function takes a pointer to data and returns another. Typically, the calling thread uses the last parameter of thread to send input data to the thread.

  • The result of thread is returned when the function that has created the thread ends. The thread returns a pointer with the results of the excutions, that may processed later with with pthread_join from the parent thread.

When the code compiles, and runs, as you will see different messages generated by each thread independently. That's typical of concurrent programming as each thread runs at a different speed.

$ gcc pthreads_create_join_main.c  -pthread -o pthreads_create_join_main
$ ./pthreads_create_join_main
[MAIN:-1218705728]: Starting............ 
[MAIN:-1218705728]: Thread allocated 
[TH_1:-1218708672]: Hello from the thread 
[TH_1:-1218708672]: To exit...............
[MAIN:-1218705728]: Thread returns 1 

11.2.1.  A thread, a concurrent execution

Once created, each thread progresses independently from the rest. This causes each of the threads can potentially travel at a different speed, running "concurrently" with the rest. This causes the execution of a program to be different on each pass.

To show a practical example, the following code illustrates this effect in a practical way, which playing with the above example and with different periods of sleep stop getting messages to be different:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// compile with $ gcc -Wall -g *.c -pthread -o program
// run with ./program
// check with valgrind --tool=helgrind ./program
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

void* thread_run(void* data)
{ //sleep(2); 
  printf("[TH_1 %ld]: Hello from the thread \n",pthread_self());
  sleep(1);
  (*(int*)data)++;
  printf("[TH_1 %ld]: To exit...............\n",pthread_self());
  pthread_exit(data);
}

int main()
{
  pthread_t thread;
  int data=0;
  int thread_rc=0;
  printf("[MAIN %ld]: Starting............ \n",pthread_self());
  if ((thread_rc=pthread_create(&thread,NULL,thread_run,&data))!=0)
  {
    printf("Error creating the thread. Code %i",thread_rc);
    return -1;
  }
  sleep(1);
  printf("[MAIN %ld]: Thread allocated \n",pthread_self());
  int *ptr_output_data;
  pthread_join(thread,(void **)&ptr_output_data);
  printf("[MAIN %ld]: Thread returns %d \n",pthread_self(),*ptr_output_data);
  return 0;
} 

When the code compiles, and runs, as you will see different messages generated by each thread independently. That's typical of concurrent programming as each thread runs at a different speed.

 ./pthreads_create_join_main 
[MAIN -1219377472]: Starting............ 
[TH_1 -1219380416]: Hello from the thread 
[MAIN -1219377472]: Thread allocated 
[TH_1 -1219380416]: To exit...............
[MAIN -1219377472]: Thread returns 1 

In this version, the code has played with sleep times of application messages that main thread and the secondary thread to be interleaved. This is achieved by inserting a sleep that generates a predictable sequence which in principle it is not so easy to achieve.

Some of the properties that can be controlled include the particularisation of the following parameters:

  • The configuration of a parent thread, if it waits or not for a result of its thread as pthread_join is invoked. This default configuration may be changed by pthread_attr_setdetachstate.

  • The size of the stack where the running thread is executing. This may be controlled with pthread_attr_setstacksize.