20160201:TP:C:Threads:Basis
Sommaire
Introduction
This week session is dedicated to multi-threading, it's intended to be an introduction to basic multi-threading.
Provided Makefile
Here is the simple Makefile that will be able to compile all questions from this session.
# Makefile CC=gcc -fsanitize=address CPPFLAGs= CFLAGS= -Wall -Wextra -std=c99 -O2 -pthread LDFLAGS= LDLIBS= all: clean: rm -f *.o # END
Hellos world
The first step is to start multiple threads. You'll need the following functions:
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); void pthread_exit(void *retval);
Our goal is to start a certain number of threads (we take the number on the command line) and make them print a simple message.
V1: all the same
The first version is pretty simple: we want to print Hello from thread ! in each thread.
Your program must behave like this:
shell> make hello_simple gcc -fsanitize=address -Wall -Wextra -std=c99 -O2 -pthread hello_simple.c -o hello_simple shell> ./hello_simple 2 Hello from thread ! Hello from thread ! shell> ./hello_simple 8 Hello from thread ! Hello from thread ! Hello from thread ! Hello from thread ! Hello from thread ! Hello from thread ! Hello from thread ! Hello from thread !
In order to manage creation and join, you need to keep track of thread handles, an array of phtread_t is the best choice.
// pseudo code for example only ! pthread_t thread_handles[42]; // ... int e; // run 3rd thread e = pthread_create(thread_handles + 3, NULL, hello, "I'm dummy code copier"); // hello(arg) is the thread start routine, // its argument should be the string to be printed if (e != 0) // oups something goes wrong ... abort(); // use better error handling ...
Note about error handling: pthread_create(3) does not (may not?) set errno like traditional syscall, if you want to use err(3) in order to have a correct error display, you have to set errno to the return value of pthread_create(3).
V2: identified thread
Now, we want something similar, but each thread will have an id number (provided by the thread creator.)
Again, here is an expected output:
shell> make hello_id gcc -fsanitize=address -Wall -Wextra -std=c99 -O2 -pthread hello_id.c -o hello_id shell> ./hello_id 4 <00>: Hello from thread ! <01>: Hello from thread ! <03>: Hello from thread ! <02>: Hello from thread ! shell> ./hello_id 8 <07>: Hello from thread ! <00>: Hello from thread ! <01>: Hello from thread ! <04>: Hello from thread ! <03>: Hello from thread ! <05>: Hello from thread ! <06>: Hello from thread ! <02>: Hello from thread !
In order to keep your data passing clean, the recommended way is to use a struct storing the message to be printed (even if it's the same for all threads) and the thread id.
struct th_arg { const char *msg; size_t id; };
Then, you create an array of this struct and initialize each cell with the message and a different id (use the cell index), and pass the pointer to the cell to the thread (pthread_create(3) last parameter.)
// pseudo code for example only ! struct th_arg data[42]; (data + 3)->msg = "I'm dummy code copier"; (data + 3)->id = 3; pthread_create(thread_handles + 3, NULL, hello, data + 3);
Who is reading stdin ?
What happen when two threads read from the standard input (or any other input) ? Which thread gets the input ?
The best way to solve that is to test !
Let's write an echo function:
void echo(size_t id) { int r; char buf[256]; while ( (r = read(STDIN_FILENO, buf, 255)) ) { if (r == -1) { if (errno == EINTR) continue; err(2, "Issue reading from stdin in thread %zu", id); } buf[r] = 0; printf("<%02zu>: %s", id, buf); } }
On the same idea as previous exercise, launch multiple threads (that will run the echo function) and see who gets inputs from stdin.
Compile, run, test, learn !