Operating systems

Portail informatique

Threads

Before starting, download this archive.

Warm up

HelloWorld! HelloWorld! HelloWorld! HelloWorld!

Write the program hello_pthread.c which creates 4 threads. Each thread displays "Hello World!".

The main thread, after creating the threads, waits for their termination.

Facebook

Throughout the class, we will work on a "fil rouge" project inspired by a real case. It sets up a web server that hosts a social network such as Facebook.

To start, get the server database:

  • git clone -b tp1_base https://gitlab.inf.telecom-sudparis.eu/csc4508/csc4508_facebook.git

This code base is composed of:

  • main.c: launches the server with the parameters chosen by the user;
  • server.c: receives HTTP requests and network connections;
  • process_requests.c : process the requests: depending on the page requested, a response (i.e. HTML) is generated to be displayed in the user's browser.

Testing the Facebook server

After compiling the server, start it with ./server -p 8080, and connect from a web browser to the address http://localhost:8080. The server crashes and displays "error: parse_url is not implemented!".

You can also test the server with the script test_suite/test_suite.sh. To run the test suite:

$ cd test_suite $ ./test_suite.sh tp1 Testing help (url: http://localhost:8080/help) ... [...]

Using strtok

The parse_url function is responsible for reading the fields of a URI in the form "/display?user=plip&foo=plop&bar=plup" and fill in several fields of the structure struct page_request which is passed as a parameter:
  • url: the name of the requested page (in the example: display) ;
  • parameters : the list of couples (key, value) of the arguments. In the example: {{ "user", "plip"}, {"foo", "plop"}, {"bar", "plup"}} ;
  • nb_param : the number of parameters ;

First, we try to detect the name of the page, and the different arguments. The detection of the key and the value of the arguments will be done in a later question.

To test, connect to this address: http://localhost:8080/display?user=plip&foo=plop&bar=plup

Using the strtok function, implement the function parse_url. A URI is in the form: /page?param1&param2&amp:...&paramn, where the number of parameters can vary from 0 to MAX_NB_PARAMETERS. Be careful, the URI may only contain "/"

Our implementation of the parse_url function does not take into account complicated cases. In the real life, we would of course not implement the function parse_url. We would rather use the library provided by W3C .

reentrancy

We are now going to extract the couples (key, value) from the parameters. For this you can use the parse_key_value function which processes a string of the form "key = value" and populates a struct key_value structure.

Modify the parse_url function to call parse_key_value when a parameter is detected. You should come across a problem of reentrance.

Fix the reentrancy problem by using strtok_r instead of strtok.

using threads

The handle_get function is called each time a client requests a page. Depending on the page requested, the processing may be long and delay the processing of other incoming requests .

Modify the handle_get function so that it creates a new thread responsible for processing the request. Remember to detach the thread (man pthread_attr_setdetachstate) so that its resources are automatically free when it finished.

thread-safety

Since client requests are handled by different threads, the processing performed must be able to be done concurrently.

Analyze the source code to determine the shared data structures, and protect them using a mutex or atomic operations.

The solution for this exercise is available in the tp1_corrige branch of the git repository.

Debugging a multi-threaded program

The exo-gdb.c program creates 4 threads which each write a message ("Hello from thread x") in one of the slots of the array. After the termination of the threads, the main thread displays the contents of the table.

We would expect that the program displays:

Thread 0 wrote: Hello from thread 0 Thread 1 wrote: Hello from thread 1 Thread 2 wrote: Hello from thread 2 Thread 3 wrote: Hello from thread 3

Run the program and see that it does not display what we expect.

$ ./exo-gdb Thread 0 wrote: Thread 1 wrote: Thread 2 wrote: Hello from thread 2 Thread 3 wrote: Hello from thread 3

To determine the cause of the problem, let's use gdb (to help you, the main useful commands for gdb are grouped here ):

  • Load the program into gdb (you may have need to modify the Makefile and add -g to the compilation flags, so that the debug symbols are included in the executable)
  • Add a breakpoint at line 11, then run the program.
  • Once the breakpoint is reached, print the source code located around the breakpoint, and display the variable values.
  • Display the backtrace of the current thread.
  • Display the list of threads, then for each threads, display the values ​​of the variables. You should notice a problem. Fix the problem.

It is necessary to add the option -g in the CFLAGS of the Makefile.

$ gdb ./exo-gdb (gdb) b exo-gdb.c:11 Breakpoint 1 at 0x118a: file exo-gdb.c, line 11. (gdb) r Starting program: /tmp/tp1/exo-gdb/exo-gdb [Thread debugging using libthread_db enabled] Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1". [New Thread 0x7ffff7db4700 (LWP 26251)] [New Thread 0x7ffff75b3700 (LWP 26252)] [New Thread 0x7ffff6db2700 (LWP 26253)] [Switching to Thread 0x7ffff7db4700 (LWP 26251)] Thread 2 "exo-gdb" hit Breakpoint 1, thread_function (arg=0x7fffffffdcd4) at exo-gdb.c:11 11 snprintf(array[my_id], 1024, "Hello from thread %d", my_id); (gdb) list 6 int nb_threads=4; 7 char array[4][1024]; 8 9 void* thread_function(void* arg){ 10 int my_id = *(int*)arg; 11 snprintf(array[my_id], 1024, "Hello from thread %d", my_id); 12 13 sleep(1); 14 return NULL; 15 } (gdb) p my_id $1 = 1 (gdb) bt #0 thread_function (arg=0x7fffffffdcd4) at exo-gdb.c:11 #1 0x00007ffff7f80fa3 in start_thread (arg=) at pthread_create.c:486 #2 0x00007ffff7eb182f in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:95 (gdb) info threads Id Target Id Frame 1 Thread 0x7ffff7db5740 (LWP 26247) "exo-gdb" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78 * 2 Thread 0x7ffff7db4700 (LWP 26251) "exo-gdb" thread_function (arg=0x7fffffffdcd4) at exo-gdb.c:11 3 Thread 0x7ffff75b3700 (LWP 26252) "exo-gdb" thread_function (arg=0x7fffffffdcd4) at exo-gdb.c:11 4 Thread 0x7ffff6db2700 (LWP 26253) "exo-gdb" clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:78 (gdb) thread 2 [Switching to thread 2 (Thread 0x7ffff7db4700 (LWP 26251))] #0 thread_function (arg=0x7fffffffdcd4) at exo-gdb.c:11 11 snprintf(array[my_id], 1024, "Hello from thread %d", my_id); (gdb) p my_id $3 = 1 (gdb) thread 3 [Switching to thread 3 (Thread 0x7ffff75b3700 (LWP 26252))] #0 thread_function (arg=0x7fffffffdcd4) at exo-gdb.c:11 11 snprintf(array[my_id], 1024, "Hello from thread %d", my_id); (gdb) p my_id $4 = 1

Threads 2 and 3 have the same identifier my_id! When examining the code, we see that the value of my_id is calculated from i. There is a race condition: between the call to pthread_create and the instruction my_id = * (int *)arg, the value of i may change!

To fix this problem, a solution can be to create an array in which we store the arguments:

int args[nb_threads]; for(int i=0; i<nb_threads; i++) { args[i] = i; pthread_create(&tid[i], NULL, thread_function, &args[i]); }

Mr and Mrs

In this exercise, we propose to write a "Mr. and Mrs." server:

  • the client reads, on their command line, the name of family of "Mr. and Mrs." It sends it to the server.
  • the server looks for this name in the file mEtMme.txt :
    • if found, it returns the name of the son, the daughter or the many children;
    • otherwise it returns the message "Sorry, I do not know";
  • the client displays the message received from the server.

To do this, you have a client / server architecture in which:

  • the client opens a message queue whose name is a function of its pid (so as not to interfere with other clients).
  • the client connects to the server via another message queue (defined by the server).
  • the client's request contains not only the "Mr. and Mrs.", but also the name of the message queue which was created in 1. by the client and on which the client waits for the response from the server.

Currently, only one thread receives requests and process them. Edit server.c so that for each new request a thread is created for processing the request.

The search_key function is not thread-safe . Identify the problem and fix it.

Instead of creating a thread for each new request, we now want requests to be handled by a pool of threads. Modify the server so that it creates a pool 10 threads at startup, and use a semaphore.