adam
adam

Reputation: 405

pthread does not exit in C

I am writing a multi-threaded application that reads a file and seeks for a word in chunks of it a thread has in memory.

A thread needs to asynchronously close other threads looking for that word if it is first to find it.

The problem is when a word is found and other threads are being closed the program does not terminate (in 6 out of 10 executions). I have checked in gdb that one thread does not exit. It happens even when I do not call waitforthreads(n_threads).

// [...]
FILE* f;
pthread_mutex_t mutex;
pthread_t* threads;
int n_threads;
int allread;
// [...]

int main(int argc, char* argv[]) {
  // [...]
  threads = (pthread_t*) calloc(n_threads, sizeof(pthread_t));
  pthread_mutex_init(&mutex, NULL);

  runthreads(f, word, n_threads, n_records);
  waitforthreads(n_threads);

  pthread_mutex_destroy(&mutex);
  // [...]
}


void runthreads(FILE* f, char* w, int n_threads, int n_records) {
  struct targs_t args = {w, n_records};
  for (int i=0; i<n_threads; i++)
    pthread_create(&threads[i], NULL, findword, (void*) &args);
}


void waitforthreads(int N) {
  for (int i=0; i<N; i++)
    if(pthread_join(threads[i], NULL))
      exit_(6);
}


void* findword(void* arg) {
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

  struct targs_t* args = (struct targs_t*) arg;
  int max_length = args->n_records * sizeof(record_t);
  record_t* records = malloc(max_length);

  int found = 0;
  while (!allread && !found) {
    pthread_mutex_lock(&mutex);
    // allread is being set in the function below 
    // if the whole file has been read
    readRecords((char*) records, args->n_records, f);
    pthread_mutex_unlock(&mutex);

    for (int i=0; i<args->n_records; i++)
      if (strlen(records[i].text) == 0) break;
      else if (strstr(records[i].text, args->word) != NULL) {
        notifyfound(pthread_self(), records[i].id);
        found = 1;
        break;
      }
  }
  free(records);
  return NULL;
}


void notifyfound(pthread_t tid, int id) {
  printf("Found: %d (%ld)\n", id, (long) tid);
  for (int i=0; i<n_threads; i++)
    if (threads[i] && !pthread_equal(threads[i], tid)) {
      printf(" closing %ld\n", (long) threads[i]);
      pthread_cancel(threads[i]);
    }
  printf(" finished closing\n");
}

Upvotes: 0

Views: 1574

Answers (1)

bluesawdust
bluesawdust

Reputation: 85

This has to do with cancellation points, although the specifics are hard to come by since you haven't shared a minimal example. My diagnosis is either

  1. 6/10 times you have at least one thread waiting for a mutex, and other one in readRecords, which will cancel and not free the mutex. Setup cancellation handlers with pthread_cleanup_push and pthread_cleanup_pop which will free your mutex, and read the manual for pthread_cancel. See related pthread_cleanup_push causes Syntax error for some references.

  2. Some of your threads are not detecting the cancellation - try using pthread_testcancel to setup a guaranteed cancellation point.

Here is some code that fixes these sorts of problems, by adding a cancellation check and mutex cleanup.

#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>

FILE* f;
pthread_mutex_t mutex;
pthread_t* threads;
int n_threads = 3;
int allread;
long int count = 0;
int *thread_ids;
int global_quit = 0;
#define MAX 99999

void waitforthreads(int N) {
  printf("waiting for %d threads\n", N);
  for (int i=0; i<N; i++)
    {
      printf("thread %d | %d\n", i, threads[i]);
      if(pthread_join(threads[i], NULL))
        {
          printf("problem\n");
          exit(6);
        }
    }
  printf("done.\n");
}

void notifyfound(pthread_t tid, int count) {
  printf("%d | %d got big number\n", count, pthread_self());
  for (int i=0; i<n_threads; i++)
    if (threads[i] && !pthread_equal(threads[i], tid)) {
      printf(" closing '%ld'\n", (long) threads[i]);
      pthread_cancel(threads[i]);
    }
  global_quit = 1;
  printf(" finished closing\n");
}

void waiting_thread_cleanup(void *arg)
{
  pthread_mutex_unlock((pthread_mutex_t *)arg);
}

void* do_thing(void* arg) {
  pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS, NULL);
  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

  int* id = (int *)arg;
  int quit = 0;
  while (!allread) {
    pthread_mutex_lock(&mutex);
    pthread_cleanup_push(waiting_thread_cleanup, (void *)&mutex); /* must be paired with pop. */
    if(count++==MAX)
      {
        notifyfound(pthread_self(), *id);
        quit=1;
      }
    else if(count % 10000 == 0)
      printf("[%d] - %d\n", *id, count);
    pthread_testcancel();       /* required to allow for the cancel to ever be 'detected' other functions are sufficient as well. */
    pthread_mutex_unlock(&mutex);
    pthread_cleanup_pop(1);     /* if this isn't here, this will occassionally hand because the mutex isn't freed. */
    if(quit==1)
      {
        printf("%d | %d quitting\n", *id, pthread_self());
        break;
      }
  }
  return NULL;
}

void runthreads(FILE* f, int n_threads) {
  for (int i=0; i<n_threads; i++)
    pthread_create(&threads[i], NULL, do_thing, &(thread_ids[i]));
}

int main(int argc, char* argv[]) {
  threads = (pthread_t*) calloc(n_threads, sizeof(pthread_t));
  thread_ids = (int*) calloc(n_threads, sizeof(int));
  for(int i=0;i<n_threads;i++)
    thread_ids[i] = i;

  pthread_mutex_init(&mutex, NULL);

  runthreads(f, n_threads);
  waitforthreads(n_threads);

  pthread_mutex_destroy(&mutex);
}

Upvotes: 1

Related Questions