【问题标题】:segfault in pthread_join()pthread_join() 中的段错误
【发布时间】:2017-10-19 14:24:25
【问题描述】:

以下程序使用每个 GROUPSIZE 文件的 1 个工作线程处理 nbFiles 文件。并行运行的工作线程不超过MAXNBRTHREADSwatchDog() 线程(线程 0)用于引导 PTHREAD_CANCEL_DEFERRED 相同的工人。如果任何一个worker失败,它pthread_cond_signal(&errCv)watchDog在全局互斥锁mtx的保护下,通过errIndc谓词传递它的线程ID。 watchDog 然后取消所有正在运行的线程(全局oldest 维护仍然存活的最旧线程的 ID 以帮助它执行此操作),并退出程序。

// compile with: gcc -Wall -Wextra -Wconversion -pedantic -std=c99 -g -D_BSD_SOURCE -pthread -o pFiles pFiles.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <stdint.h>
#include "pthread.h"

#define INDIC_ALL_DONE_OK  -1

typedef  int_fast32_t  int32;
typedef uint_fast32_t uint32;

uint32 MAXNBRTHREADS = 10; // no more than this amount of threads running in parallel
uint32 GROUPSIZE = 10;   // how many files per thread

uint32 nbFiles, gThID;  // total #files, group ID for a starting thread

int32 errIndc = 0;  // global thread error indicator

pthread_t *thT;     // pthread table
void **retVals;     // thread ret. val. table, needed in stop_watchDog()
uint32 gThCnt;   // calculated size of thT[]
uint32 thCnt, oldest;  // running threads count (as they are created), oldest thread *alive*

pthread_cond_t  errCv = PTHREAD_COND_INITIALIZER;  // thread-originated error signal
pthread_mutex_t mtx   = PTHREAD_MUTEX_INITIALIZER; // mutex to protect errIndc

// Worker thread
void *processFileGroup(void *arg) {
  int32 err;
  int last_state, last_type;
  uint32 i, thId = (uint32)(intptr_t) arg;

  fprintf(stderr, "th %ld started\n", thId);

  pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &last_state);
  pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, &last_type);

  // Artificial error in thread 17
  if(thId==17) {
    pthread_mutex_lock(&mtx);
      errIndc = (int32) thId;
      pthread_cond_signal(&errCv);
    pthread_mutex_unlock(&mtx);
    pthread_exit((void *)(intptr_t)err); }

  for(i = 0; i < GROUPSIZE ; i++) {  // simulate processing GROUPSIZE files
    pthread_testcancel();
    err = 0;
    if(usleep(10000)) { err = 1; break; }
  }

  //fprintf(stderr, "  -- th %ld done with err = %ld\n", thId, err);
  if(err!=0) { // Signal watch dog
    pthread_mutex_lock(&mtx);
      errIndc = (int32) thId;
      pthread_cond_signal(&errCv);
    pthread_mutex_unlock(&mtx);
    pthread_exit((void *)(intptr_t) err);
  }

  pthread_exit((void *)(intptr_t) err);
}

// Mishap : cancel existing threads, exit program
int32 cancel_exit(int32 rc, int32 faultyThId, char *msg) {
  uint32 j; int32 rval;
  void *retVal;
  if(rc==0) return 0;
  if(msg!=NULL && msg[0]=='\0') fprintf(stderr, "\nError in thread %ld. Stoping..\n", faultyThId);
  else                          fprintf(stderr, "\n%s %ld. Stop.\n\n", msg, faultyThId);
  for(j = oldest; j < thCnt ; j++) pthread_cancel(thT[j]);
  for(j = oldest; j < thCnt ; j++){
    pthread_join(thT[j], &retVal); rval = (int)(intptr_t) retVal;
    //if(retVal == PTHREAD_CANCELED || rval==115390242)
    if(retVal == PTHREAD_CANCELED)
         fprintf(stderr, "  cexit: thread %ld canceled\n", j);
    else fprintf(stderr, "  cexit: thread %ld finished, rc = %ld\n", j, rval);
  }
  pthread_join(thT[4], &retVal); rval = (int)(intptr_t) retVal; fprintf(stderr, "  cexit1: thread 4 finished, rc = %ld\n", rval);
  fprintf(stderr, "Processing stopped\n\n");
  exit(EXIT_FAILURE); return rc;
}

// Watch dog thread
// it fires on signal from one of the running threads about a mishap
void *watchDog(void *arg) {
  int32 err;
  pthread_mutex_lock(&mtx);
    while (errIndc == 0) {
      pthread_cond_wait(&errCv,&mtx);
      if(errIndc == INDIC_ALL_DONE_OK){   // main() says we're done with no issues
        pthread_mutex_unlock(&mtx);
        err = 0; pthread_exit((void *)(intptr_t) err);
      }
    }
  pthread_mutex_unlock(&mtx);
  fprintf(stderr, "watch dog: stopping on error indication %ld\n", errIndc);
  cancel_exit(1, errIndc, "");
  exit(EXIT_FAILURE); return arg;// not reached
}

void stop_watchDog() {
  pthread_mutex_lock(&mtx);
    errIndc = INDIC_ALL_DONE_OK;
    pthread_cond_signal(&errCv);
  pthread_mutex_unlock(&mtx);
  pthread_join(thT[0], &retVals[0]);
}

int main() {

  uint32 i, k;
  int32 rc;

  nbFiles = 950;
  gThCnt = 1+nbFiles/GROUPSIZE;

  if(gThCnt > MAXNBRTHREADS)
    fprintf(stderr, "running max %ld threads in parallel\n", MAXNBRTHREADS);
  else fprintf(stderr, "using %ld worker thread(s)\n", gThCnt);

  gThCnt++; // account for watchDog (thread 0)

  thT = (pthread_t *) calloc(gThCnt, sizeof(pthread_t));  if(thT==NULL) { perror("calloc"); exit(EXIT_FAILURE); }
  retVals = (void **) calloc( (nbFiles/GROUPSIZE), sizeof(void *));  if(retVals==NULL) { perror("calloc"); exit(EXIT_FAILURE); }

  // Start watch dog
  rc = pthread_create(&thT[0], NULL, watchDog, NULL);
  if(rc != 0) { fprintf(stderr,"pthread_create() failed for thread 0\n"); exit(EXIT_FAILURE); }
  thCnt = 1;

  i = 0; oldest = 1;
  while(thCnt<gThCnt) {
    pthread_mutex_lock(&mtx);
      if(errIndc != 0){   // watchDog is already tearing down the whole system, no point in creating more threads
        pthread_join(thT[0], &retVals[0]); // wait on WatchDog thread, which never returns (it cancel_exists).
        exit(EXIT_FAILURE);  // not reached
      }
    pthread_mutex_unlock(&mtx);

    gThID = thCnt;
    rc = pthread_create(&thT[thCnt], NULL, processFileGroup, (void *)(intptr_t) gThID);
    if(rc != 0) {
      fprintf(stderr,"pthread_create() failed for thread %ld\n", thCnt);
      stop_watchDog();
      cancel_exit(1, (int32)thCnt, "Could not create thread");
    }
    thCnt++;
    if(thCnt>MAXNBRTHREADS) {  // wait for the oldest thread to finish
      pthread_mutex_lock(&mtx);
        if(errIndc != 0) {   // watchDog is already tearing down the whole system, he'll report the rc of thread "oldest"
          printf("[MAXNBRTHREADS] errIndc=%ld, joining watchDog\n", errIndc);
          pthread_join(thT[0], &retVals[0]); // wait on WatchDog thread, which never returns (it cancel_exists).
          exit(EXIT_FAILURE);  // not reached
        }
      pthread_mutex_unlock(&mtx);
      pthread_join(thT[oldest], &retVals[oldest]); rc = (int)(intptr_t) retVals[oldest];
      fprintf(stderr, "[MAXNBRTHREADS] Thread %ld done with rc = %ld\n", oldest, rc);
      oldest++;
    }
  }
  k = oldest;
  while(k<thCnt) {
    pthread_mutex_lock(&mtx);
      if(errIndc != 0){   // watchDog is already tearing down the whole system, he'll report the rc of thread k
        pthread_join(thT[0], &retVals[0]); // wait on WatchDog thread, which never returns (it cancel_exists).
        exit(EXIT_FAILURE);  // not reached
      }
    pthread_mutex_unlock(&mtx);
    pthread_join(thT[k], &retVals[k]); rc = (int)(intptr_t) retVals[k];
    fprintf(stderr, "Thread %ld done with rc = %ld\n", k, rc);
    oldest = ++k;
  }

  // Signal watch dog to quit
  stop_watchDog();

  exit(EXIT_SUCCESS);

}

第 82 行导致该程序出现段错误。为什么 ?加入已取消的线程是否违法?

如果您评论第 82 行,则会出现其他问题。如果您运行程序 4 次中的 3 次,您会看到以下病理结果之一:

线程 11 怎么会有两个不同的退出代码?

.. 
watch dog: stopping on error indication 17

Error in thread 17. Stoping..
th 19 started
  cexit: thread 11 finished, rc = 115390242
[MAXNBRTHREADS] Thread 11 done with rc = -1

有时程序会在 MAXNBRTHREADS 部分挂起:

...
[MAXNBRTHREADS] errIndc=17, joining watchDog

这部分显然存在竞争条件;但我想不通。

任何帮助表示赞赏。

【问题讨论】:

  • 好吧,我会改变一些东西,让它更干净、更安全、更容易调试。整个设计,大概;(
  • @Martin:好吧,我将必须处理的文件分派给一堆线程,将自己限制在最大线程数。如果任何线程失败,整个过程就毫无意义,所以我让他们可以取消。我的设计是用看门狗来做.. 狗看和阻止牛群以防万一。这是我能为我的用例想出的最简单的设计。欢迎任何有用的想法。
  • 加入取消的线程就好了,应该不是segmentation fault的原因。看这里stackoverflow.com/questions/8975395/…

标签: c multithreading pthreads


【解决方案1】:

你问:

第 82 行导致该程序出现段错误。为什么 ?加入已取消的线程是否违法?

POSIX 并没有用很多话来说明这一点,但它似乎确实暗示了这一点。 The specifications for pthread_join()说:

如果 pthread_join() 的线程参数指定的值不引用可连接线程,则行为未定义。

后来,在 RATIONALE 中,

如果实现在其结束后检测到线程 ID 的使用 生命周期,建议函数失败并报告 [ESRCH] 错误。

您观察到的段错误与基本原理中的(非规范性)建议不一致,但基本原理确实支持线程在其生命周期结束后不再是“可连接线程”的命题(例如,因为它具有已取消),否则建议将与函数的指定行为不一致。当然,已经加入的线程不再可加入,尽管使用“可加入”而不是“实时”或类似的原因可能更多的是用于分离线程的规定。

线程 11 怎么会有两个不同的退出代码?

它不能,并且您的输出没有另外说明。您要加入线程 11 两次,因此至少有一个 pthread_join() 调用必须失败。如果确实如此,您不能依赖它可能存储的任何结果值(无论如何,不​​是基于 POSIX)。您应该检查函数调用的返回值是否有错误标志。

有时程序会在 MAXNBRTHREADS 部分挂起

是的,看来可以。

这里的想法似乎是在失败的情况下,主线程将调用stop_watchDog(),它会设置一个标志来通知看门狗线程它应该停止,然后向条件变量发出信号以使看门狗唤醒起来并注意它。当它唤醒时,看门狗线程必须重新获取互斥锁mtx,然后才能从pthread_cond_wait()返回。

stop_watchDog()返回后,主线程锁定互斥锁mtx和 尝试加入看门狗线程。但是发送 CV 信号不是同步的。因此,主线程可能会在看门狗线程重新获取互斥锁之前锁定它,在这种情况下您将死锁:看门狗无法从pthread_cond_wait()返回并继续终止,直到它获取互斥锁,但主线程不会解锁互斥锁,直到看门狗终止。

我没有对程序进行足够的分析,无法确定主线程需要在那里保护的确切状态,尽管它似乎至少包含errIndc 变量。然而,无论如何,它似乎不需要在尝试加入看门狗线程时保持互斥锁锁定。

【讨论】:

  • 非常感谢,您的回答帮助我解决了代码中的所有问题。你是对的,双联是不行的。这回答了段错误 pb。下一个错误: err 在工作程序的“人为错误”部分中未初始化。呃。确实,加入看门狗是不需要加锁的。仅在读取 errInc (原子地)时才需要锁定。但这并不能保证它以后不会改变,如果发生这种情况 main() 和 watchDog() 都将加入() ..
  • 解决方案是简单地使用专用互斥锁保护对oldest 的所有访问(尤其是在cancel_exit() 中)。之后问题就消失了。这解决了main()cancel_exit() 在一瞬间加入同一个线程的问题。
  • 去掉简历后,就是这样。对于 Martin James 来说,有一个更简单的设计:根本不需要条件变量,在这个用例中互斥体就足够了。
猜你喜欢
  • 2016-06-25
  • 2018-11-06
  • 2015-05-02
  • 1970-01-01
  • 2017-04-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多