20160201:TP:C:Threads:Basis

De wiki-prog
Aller à : navigation, rechercher


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 !