threads

why threads?

  • concurrency: different things happening at once

    • one thread per user of web server?
    • one thread per page in web browser?
    • one thread to play audio, one to read keyboard, …?
  • parallelism: do same thing with more resources

    • multiple processors to speed-up simulation (life assignment)

single and multithread processes

pthread_create

void *ComputePi(void *argument) { ...  }
void *PrintClassList(void *argument) { ...  }
int main() {
  pthread_t pi_thread, list_thread;
  if (0 != pthread_create(&pi_thread, NULL, ComputePi, NULL))
      handle_error();
  if (0 != pthread_create(&list_thread, NULL, PrintClassList, NULL))
      handle_error();
  ... /* more code */
}

pthread_create

void *ComputePi(void *argument) { ...  }
void *PrintClassList(void *argument) { ...  }
int main() {
  pthread_t pi_thread, list_thread;
  if (0 != pthread_create(&pi_thread, NULL, ComputePi, NULL))
      handle_error();
  if (0 != pthread_create(&list_thread, NULL, PrintClassList, NULL))
      handle_error();
  ... /* more code */
}
  • pthread_create arguments:
  • thread identifier
  • function to run thread starts here, terminates if this function returns
  • thread attributes (extra settings) and function argument

a threading race

#include <pthread.h>
#include <stdio.h>
void *print_message(void *ignored_argument) {
    printf("In the thread\n");
    return NULL;
}
int main() {
    printf("About to start thread\n");
    pthread_t the_thread;
    /* assume does not fail */
    pthread_create(&the_thread, NULL, print_message, NULL);
    printf("Done starting thread\n");
    return 0;
}

My machine: outputs In the thread about 4% of the time.
What happened?

a race

  • returning from main exits the entire process (all its threads)
    • same as calling exit; not like other threads
  • race: main’s return 0 or print_message’s printf first?

fixing the race (version 1)

#include <pthread.h>
#include <stdio.h>
void *print_message(void *ignored_argument) {
    printf("In the thread\\n");
    return NULL;
}
int main() {
    printf("About to start thread\\n");
    pthread_t the_thread;
    /* missing: error checking */
    pthread_create(&the_thread, NULL, print_message, NULL);
    printf("Done starting thread\\n");
    pthread_join(the_thread, NULL);  /* WAIT FOR THREAD */
    return 0;
}

pthread_join, pthread_exit

  • R = pthread_join(X, &P): wait for thread X, copies return value into P

    • like waitpid, but for a thread
    • thread return value is pointer to anything
    • R = 0 if successful, error code otherwise
  • pthread_exit: exit current thread, returning a value

    • like exit or returning from main, but for a single thread
    • same effect as returning from function passed to pthread_create

a note on error checking

  • from pthread_create manpage:
ERRORS: EAGAIN: Insufficient resources to create another thread, or a system-imposed limit on the number of threads was encountered. The latter case may occur in two ways: the RLIMIT_NPROC soft resource limit was reached; or the kernel's system-wide limit on the number of threads,  /proc/sys/kernel/threads-max, was reached.  EINVAL: Invalid settings in attr. EPERM: No permission to set the scheduling policy and parameters specified in attr.
  • special constants for return value
  • same pattern for many other pthreads functions
    • pthread_join, pthread_mutex_… (later), …
  • will often omit error checking in slides for brevity

error checking pthread_create

int error = pthread_create(...);
if (error != 0) {
    /* print some error message */
}

sum example (only globals)

int values[1024];  int results[2];
void *sum_front(void *ignored_argument) {
    int sum = 0;
    for (int i = 0; i < 512; ++i) { sum += values[i]; }
    results[0] = sum;
    return NULL;
}
void *sum_back(void *ignored_argument) {
    int sum = 0;
    for (int i = 512; i < 1024; ++i) { sum += values[i]; }
    results[1] = sum;
    return NULL;
}
int sum_all() {
    pthread_t sum_front_thread, sum_back_thread;
    /* missing: error handling */
    pthread_create(&sum_front_thread, NULL, sum_front, NULL);
    pthread_create(&sum_back_thread, NULL, sum_back, NULL);
    pthread_join(sum_front_thread, NULL); pthread_join(sum_back_thread, NULL);
    return results[0] + results[1];
}

sum example (only globals)

int values[1024];  int results[2];
void *sum_front(void *ignored_argument) {
    int sum = 0;
    for (int i = 0; i < 512; ++i) { sum += values[i]; }
    results[0] = sum;
    return NULL;
}
void *sum_back(void *ignored_argument) {
    int sum = 0;
    for (int i = 512; i < 1024; ++i) { sum += values[i]; }
    results[1] = sum;
    return NULL;
}
int sum_all() {
    pthread_t sum_front_thread, sum_back_thread;
    /* missing: error handling */
    pthread_create(&sum_front_thread, NULL, sum_front, NULL);
    pthread_create(&sum_back_thread, NULL, sum_back, NULL);
    pthread_join(sum_front_thread, NULL); pthread_join(sum_back_thread, NULL);
    return results[0] + results[1];
}

values, results: global variables
shared between all threads

sum example (only globals)

int values[1024];  int results[2];
void *sum_front(void *ignored_argument) {
    int sum = 0;
    for (int i = 0; i < 512; ++i) { sum += values[i]; }
    results[0] = sum;
    return NULL;
}
void *sum_back(void *ignored_argument) {
    int sum = 0;
    for (int i = 512; i < 1024; ++i) { sum += values[i]; }
    results[1] = sum;
    return NULL;
}
int sum_all() {
    pthread_t sum_front_thread, sum_back_thread;
    /* missing: error handling */
    pthread_create(&sum_front_thread, NULL, sum_front, NULL);
    pthread_create(&sum_back_thread, NULL, sum_back, NULL);
    pthread_join(sum_front_thread, NULL); pthread_join(sum_back_thread, NULL);
    return results[0] + results[1];
}

two functions — same except some numbers

values returned from via global array results
used here instead of return value
(partly to illustrate memory is shared;
partly for later version that doesn’t join)

thread_sum memory layout

sum example (to global, with thread IDs)

int values[1024];
int results[2];
void *sum_thread(void *argument) {
    int id = (int) argument;
    int sum = 0;
    for (int i = id * 512; i < (id + 1) * 512; ++i) {
        sum += values[i];
    }
    results[id] = sum;
    return NULL;
}
int sum_all() {
    /* missing: error handling */
    pthread_t thread[2];
    for (int i = 0; i < 2; ++i) {
        pthread_create(&threads[i], NULL, sum_thread, (void *) i);
    }
    for (int i = 0; i < 2; ++i)
        pthread_join(threads[i], NULL);
    return results[0] + results[1];
}

pass thread index (as fake pointer “address”)

sum example (info struct)

int values[1024];
struct ThreadInfo {
    int start, end, result;
};
void *sum_thread(void *argument) {
    struct ThreadInfo *my_info = (struct ThreadInfo *) argument;
    int sum = 0;
    for (int i = my_info->start; i < my_info->end; ++i) { sum += values[i]; }
    my_info->result = sum;
    return NULL;
}
int sum_all() {
    pthread_t thread[2]; struct ThreadInfo info[2];
    for (int i = 0; i < 2; ++i) {
        info[i].start = i*512; info[i].end = (i+1)*512;
        pthread_create(&threads[i], NULL, sum_thread, &info[i]);
    }
    for (int i = 0; i < 2; ++i) { pthread_join(threads[i], NULL); }
    return info[0].result + info[1].result;
}

sum example (info struct)

int values[1024];
struct ThreadInfo {
    int start, end, result;
};
void *sum_thread(void *argument) {
    struct ThreadInfo *my_info = (struct ThreadInfo *) argument;
    int sum = 0;
    for (int i = my_info->start; i < my_info->end; ++i) { sum += values[i]; }
    my_info->result = sum;
    return NULL;
}
int sum_all() {
    pthread_t thread[2]; struct ThreadInfo info[2];
    for (int i = 0; i < 2; ++i) {
        info[i].start = i*512; info[i].end = (i+1)*512;
        pthread_create(&threads[i], NULL, sum_thread, &info[i]);
    }
    for (int i = 0; i < 2; ++i) { pthread_join(threads[i], NULL); }
    return info[0].result + info[1].result;
}

my_info = pointer to sum_all’s stack
okay because sum_all doesn’t return until thread is done

thread_sum memory layout (info struct)

sum example (to main stack)

struct ThreadInfo { int *values; int start; int end; int result };
void *sum_thread(void *argument) {
    ThreadInfo *my_info = (ThreadInfo *) argument;
    int sum = 0;
    for (int i = my_info->start; i < my_info->end; ++i) {
        sum += my_info->values[i];
    }
    my_info->result = sum;
    return NULL;
}
int sum_all(int *values) {
    ThreadInfo info[2]; pthread_t thread[2];
    for (int i = 0; i < 2; ++i) {
        info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512;
        pthread_create(&threads[i], NULL, sum_thread, (void *) &info[i]);
    }
    for (int i = 0; i < 2; ++i)
        pthread_join(threads[i], NULL);
    return info[0].result + info[1].result;
}

program memory (to main stack)

sum example (on heap)

struct ThreadInfo {
    pthread_t thread;
    int *values; int start; int end; int result;
};

void *sum_thread(void *argument) { ...  }

struct ThreadInfo *start_sum_all(int *values) {
    struct ThreadInfo *info = calloc(2, sizeof(struct ThreadInfo));
    for (int i = 0; i < 2; ++i) {
        info[i].values = values; info[i].start = i*512; info[i].end = (i+1)*512;
        pthread_create(&info[i].thread, NULL, sum_thread, (void *) &info[i]);
    }
    return info;
}

int finish_sum_all(ThreadInfo *info) {
    for (int i = 0; i < 2; ++i)
        pthread_join(info[i].thread, NULL);
    int result = info[0].result + info[1].result;
    free(info);
    return result;
}

thread_sum memory (heap version)

what’s wrong with this?

/* omitted: headers */
void *create_string(void *ignored_argument) {
  char string[1024];
  ComputeString(string);
  return string;
}

int main() {
  pthread_t the_thread;
  pthread_create(&the_thread, NULL, create_string, NULL);
  char *string_ptr;
  pthread_join(the_thread, (void**) &string_ptr);
  printf("string is %s\n", string_ptr);
}

program memory

thread joining

  • pthread_join allows collecting thread return value
  • if you don’t join joinable thread, then memory leak!
  • avoiding memory leak?
  • always join… or
  • ‘‘detach’’ thread to make it not joinable

pthread_detach

void *show_progress(void * ...) { ... }
void spawn_show_progress_thread() {
    pthread_t show_progress_thread;
    pthread_create(&show_progress_thread, NULL,
                   show_progress, NULL);

    /* instead of keeping pthread_t around to join thread later: */
    pthread_detach(show_progress_thread);]
}

int main() {
    spawn_show_progress_thread();
    do_other_stuff();
    ...
}

detach = don’t care about return value, etc.
system will decallocate when thread terminates

starting threads detached

void *show_progress(void * ...) { ... }
void spawn_show_progress_thread() {
    pthread_t show_progress_thread;
    pthread_attr_t attrs;
    pthread_attr_init(&attrs);
    pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
    pthread_create(&show_progress_thread, attrs,
                   show_progress, NULL);
    pthread_attr_destroy(&attrs);
}

setting stack sizes

void *show_progress(void * ...) { ... }
void spawn_show_progress_thread() {
    pthread_t show_progress_thread;
    pthread_attr_t attrs;
    pthread_attr_init(&attrs);
    pthread_attr_setstacksize(&attrs, 32 * 1024 /* bytes */);
    pthread_create(&show_progress_thread, attrs,
                   show_progress, NULL);
}