pthreads in C – pthread_exit

出于某种原因,我认为在main函数结束时调用pthread_exit(NULL)将保证所有正在运行的线程(至少在main函数中创建)将在main退出之前完成运行。 但是,当我在不调用两个pthread_join函数(在main的末尾)运行下面的代码时,我明显得到一个分段错误,这似乎是因为main函数在两个线程完成它们的工作之前已经退出,因此char缓冲区不再可用。 但是,当我在main的末尾包含这两个pthread_join函数调用时,它应该按原样运行。 为了保证main在所有正在运行的线程完成之前不会退出,是否有必要为main直接初始化的所有线程显式调用pthread_join

 #include  #include  #include  #include  #include  #include  #define NUM_CHAR 1024 #define BUFFER_SIZE 8 typedef struct { pthread_mutex_t mutex; sem_t full; sem_t empty; char* buffer; } Context; void *Reader(void* arg) { Context* context = (Context*) arg; for (int i = 0; i full); pthread_mutex_lock(&(context->mutex)); char c = context->buffer[i % BUFFER_SIZE]; pthread_mutex_unlock(&(context->mutex)); sem_post(&context->empty); printf("%c", c); } printf("\n"); return NULL; } void *Writer(void* arg) { Context* context = (Context*) arg; for (int i = 0; i empty); pthread_mutex_lock(&(context->mutex)); context->buffer[i % BUFFER_SIZE] = 'a' + (rand() % 26); float ranFloat = (float) rand() / RAND_MAX; if (ranFloat mutex)); sem_post(&context->full); } return NULL; } int main() { char buffer[BUFFER_SIZE]; pthread_t reader, writer; Context context; srand(time(NULL)); int status = 0; status = pthread_mutex_init(&context.mutex, NULL); status = sem_init(&context.full,0,0); status = sem_init(&context.empty,0, BUFFER_SIZE); context.buffer = buffer; status = pthread_create(&reader, NULL, Reader, &context); status = pthread_create(&writer, NULL, Writer, &context); pthread_join(reader,NULL); // This line seems to be necessary pthread_join(writer,NULL); // This line seems to be necessary pthread_exit(NULL); return 0; } 

如果是这种情况,我怎么能处理使用相同的线程标识符创建大量相同线程(如下面的代码中)的情况? 在这种情况下,如何确保所有线程在main退出之前完成? 我是否真的必须保留一组NUM_STUDENTS pthread_t标识符才能执行此操作? 我想我可以通过让Student线程发出信号量信号然后让main函数等待那个信号量来做到这一点,但实际上没有更简单的方法吗?

 int main() { pthread_t thread; for (int i = 0; i < NUM_STUDENTS; i++) pthread_create(&thread,NULL,Student,NULL); // Threads // Make sure that all student threads have finished exit(0); } 

pthread_exit()是一个线程调用的函数,用于终止自己的执行。 对于你给出的情况,不要从你的主程序线程中调用它。

正如您所知, pthread_join()是等待从main()完成可连接线程的正确方法。

另外,正如您已经想到的那样,您需要保持从pthread_create()返回的值以传递给pthread_join()

这意味着如果您打算使用pthread_join() ,则不能对您创建的所有线程使用相同的pthread_t变量。

而是构建一个pthread_t数组,以便获得每个线程ID的副本。

除了程序是否应该在主线程调用pthread_exit时终止时, pthread_exit

pthread_exit()函数终止调用线程

并且:

线程终止后,访问线程的本地(自动)变量的结果是未定义的。

由于上下文是main()的自动变量,因此您的代码甚至可以在测试您想要测试的内容之前就可以解决…

迷你传奇

您没有提到运行原始代码的环境。 我修改你的代码使用nanosleep() (因为,正如我在问题的评论中提到的, sleep()采用整数,因此sleep(0.2)相当于sleep(0) ),并在MacOS X上编译程序10.6.4。

没有错误检查

它工作正常; 使用0.5概率因子运行大约需要100秒(正如您所期望的那样;我将其更改为0.05以将运行时间减少到大约10秒),并在某些时候生成随机字符串。

有时我什么也没得到,有时我得到更多,有时我得到的数据更少。 但我没有看到核心转储(甚至没有’ulimit -c unlimited’来允许任意大的核心转储)。

最后,我应用了一些工具并且看到我总是有1025个字符(1024个生成加上换行符),但很多时候,我有1024个ASCII NUL字符。 有时它们会出现在中间,有时候出现在开头等等:

 $ ./pth | tpipe -s "vis | ww -w64" "wc -c" 1025 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000\000\000\000\000\000\000\000\000 \000\000\000\000\000\000\000\000ocriexffwgdvdvyfitjtvlzcoffhusjo zyacniffpsfswesgrkuxycsubufamxxzkrkqnwvsxcbmktodessyohixsmuhdovt hhertqjjinzoptcuqzertybicrzaeyqlyublbfgutcdvftwkuvxhouiuduoqrftw xjkgqutpryelzuaerpsbotwyskaflwofseibfqntecyseufqxvzikcyeeikjzsye qxhjwrjmunntjwhohqovpwcktolcwrvmfvdfsmkvkrptjvslivbfjqpwgvroafzn fkjumqxjbarelbrdijfrjbtiwnajeqgnobjbksulvcobjkzwwifpvpmpwyzpwiyi cdpwalenxmocmtdluzouqemmjdktjtvfqwbityzmronwvulfizpizkiuzapftxay obwsfajcicvcrrjehjeyzsngrwusbejiovaaatyzouktetcerqxjsdpswixjpege blxscdebfsptxwvwsllvydipovzmnrvoiopmqotydqaujwdykidmwzitdsropguv vudyfiaaaqueyllnwudfpplcfbsngqqeyucdawqxqzczuwsnaquofreilzvdwbjq ksrouwltvaktpdrvjnqahpdqdshmmvntspglexggshqbjrvxceaqlfnukedxzlms cnapdtgtcoyhnglojbjnplowericrzbfulvrobfn $ 

(’tpipe’程序就像’tee’,但它写入管道而不是文件(除非你指定’-s’选项,否则写入标准输出);’vis’来自Kernighan&Pike的’UNIX编程环境’ ;’ww’是’word wrapper’,但这里没有任何单词,所以它的粗暴力量包裹在宽度64.)

我看到的行为非常不确定 – 每次运行都会得到不同的结果。 我甚至用字母顺序替换了随机字符(’a’+ i%26),并且仍然有奇怪的行为。

我添加了一些调试打印代码(和context->full的计数器),很明显,信号量context->full对读者来说不正常 – 在作者写了任何东西之前它被允许进入互斥。

有错误检查

当我将错误检查添加到互斥锁和信号量操作时,我发现:

 sem_init(&context.full) failed (-1) errno = 78 (Function not implemented) 

因此,奇怪的输出是因为MacOS X没有实现sem_init() 。 这很古怪; sem_wait()函数失败,错误为errno = 9(EBADF’错误的文件描述符’); 我先在那里添加了支票。 然后我检查了初始化…

使用sem_open()而不是sem_init()

sem_open()调用成功,看起来很好(名称为"/full.sem""/empty.sem" ,标记O_CREAT,模式值为0444,0600,0700在不同时间,初始值为0和BUFFER_SIZE,如同sem_init() )。 不幸的是,第一个sem_wait()sem_post()操作再次失败,并且errno = 9(EBADF’错误的文件描述符’)。

  • 检查系统调用的错误情况非常重要。
  • 我看到的输出是非确定性的,因为信号量不起作用。
  • 这并没有改变’没有pthread_join()调用’它不会崩溃’的行为。
  • MacOS X没有可用的POSIX信号量实现。

无需调用pthread_join(reader,NULL); 如果使用静态存储持续时间声明Contextbuffer (正如Steve Jessop,caf和David Schwartz已经指出的那样)。

声明Contextbuffer static也需要将Context *context分别更改为Context *contextrContext *contextw

另外,下面的重写称为pthread_exit.csem_open()替换sem_init() sem_open()并使用nanosleep() (如Jonathan Leffler所建议的那样)。

pthread_exit在Mac OS X 10.6.8上测试过,未输出任何ASCII NUL字符。

 /* cat pthread_exit.c (sample code to test pthread_exit() in main()) source: "pthreads in C - pthread_exit", http://stackoverflow.com/questions/3330048/pthreads-in-c-pthread-exit compiled on Mac OS X 10.6.8 with: gcc -ansi -pedantic -std=gnu99 -Os -Wall -Wextra -Wshadow -Wpointer-arith -Wcast-qual -Wstrict-prototypes \ -Wmissing-prototypes -Wformat=2 -l pthread -o pthread_exit pthread_exit.c test with: time -p bash -c './pthread_exit | tee >(od -c 1>&2) | wc -c' */ #include  #include  #include  #include  #include  #include  #include  void *Reader(void* arg); void *Writer(void* arg); // #define NUM_CHAR 1024 #define NUM_CHAR 100 #define BUFFER_SIZE 8 typedef struct { pthread_mutex_t mutex; sem_t *full; sem_t *empty; const char *semname1; const char *semname2; char* buffer; } Context; static char buffer[BUFFER_SIZE]; static Context context; void *Reader(void* arg) { Context *contextr = (Context*) arg; for (int i = 0; i < NUM_CHAR; ++i) { sem_wait(contextr->full); pthread_mutex_lock(&(contextr->mutex)); char c = contextr->buffer[i % BUFFER_SIZE]; pthread_mutex_unlock(&(contextr->mutex)); sem_post(contextr->empty); printf("%c", c); } printf("\n"); return NULL; } void *Writer(void* arg) { Context *contextw = (Context*) arg; for (int i = 0; i < NUM_CHAR; ++i) { sem_wait(contextw->empty); pthread_mutex_lock(&(contextw->mutex)); contextw->buffer[i % BUFFER_SIZE] = 'a' + (rand() % 26); float ranFloat = (float) rand() / RAND_MAX; //if (ranFloat < 0.5) sleep(0.2); if (ranFloat < 0.5) nanosleep((struct timespec[]){{0, 200000000L}}, NULL); pthread_mutex_unlock(&(contextw->mutex)); sem_post(contextw->full); } return NULL; } int main(void) { pthread_t reader, writer; srand(time(NULL)); int status = 0; status = pthread_mutex_init(&context.mutex, NULL); context.semname1 = "Semaphore1"; context.semname2 = "Semaphore2"; context.full = sem_open(context.semname1, O_CREAT, 0777, 0); if (context.full == SEM_FAILED) { fprintf(stderr, "%s\n", "ERROR creating semaphore semname1"); exit(EXIT_FAILURE); } context.empty = sem_open(context.semname2, O_CREAT, 0777, BUFFER_SIZE); if (context.empty == SEM_FAILED) { fprintf(stderr, "%s\n", "ERROR creating semaphore semname2"); exit(EXIT_FAILURE); } context.buffer = buffer; status = pthread_create(&reader, NULL, Reader, &context); status = pthread_create(&writer, NULL, Writer, &context); // pthread_join(reader,NULL); // This line seems to be necessary // pthread_join(writer,NULL); // This line seems to be necessary sem_unlink(context.semname1); sem_unlink(context.semname2); pthread_exit(NULL); return 0; } 

pthread_join()是等待其他线程完成的标准方法,我会坚持这一点。

或者,您可以创建一个线程计数器,让所有子线程在开始时将其递增1,然后在它们完成时将其递减1(当然正确锁定),然后让main()等待此计数器达到0。 ( pthread_cond_wait()将是我的选择)。

按照正常的pthread语义,如这里所教导的那样,你的原始想法确实似乎得到了证实:

如果main()在它创建的线程之前完成,并且使用pthread_exit()退出,则其他线程将继续执行。 否则,当main()完成时,它们将自动终止。

但是我不确定这是POSIX线程标准的一部分还是只是一个常见的但不是通用的“很好的”附加花絮(我知道有些实现不尊重这种约束 – 我只是不这样做知道这些实现是否仍然被认为是标准兼容的! – )。 因此,我必须加入谨慎的合唱团,建议加入你需要终止的每个线程,只是为了安全起见 – 或者,正如Jon Postel在TCP / IP实现的上下文中所说 :

 Be conservative in what you send; be liberal in what you accept. 

“稳健性原则”应该比TCP / IP更广泛地使用;-)。

pthread_exit(3)退出调用它的线程(但如果其他线程仍在运行,则退出整个进程)。 在您的示例中,其他线程在main的堆栈上使用变量,因此当main的线程退出并且其堆栈被销毁时,它们访问未映射的内存,从而访问段错误。

使用其他人建议的正确pthread_join(3)技术,或将共享变量移动到静态存储中。

当您向线程传递指向变量的指针时,您需要确保该变量的生命周期至少与线程将尝试访问该变量一样长。 您将线程指针传递给buffercontext ,这些指针分配在main的堆栈上。 main退出后,这些变量就不复存在了。 因此,在确认这些线程不再需要访问这些指针之前,您无法退出main

95%的情况下,解决此问题的方法是遵循以下简单模式:

1)分配一个对象来保存参数。

2)用参数填充对象。

3)将指向对象的指针传递给新线程。

4)允许新线程释放对象。

遗憾的是,这对两个或多个线程共享的对象不起作用。 在这种情况下,您可以在参数对象中放置使用计数和互斥量。 每个线程都可以在互斥锁保护完成后减少使用次数。 将使用计数降为零的线程释放对象。

您需要为buffercontext执行此操作。 将use count设置为2 ,然后将指向此对象的指针传递给两个线程。

pthread_join执行以下操作:

pthread_join()函数暂停执行调用线程,直到目标线程终止,除非目标线程已经终止。 从具有非NULL value_ptr参数的成功pthread_join()调用返回时,终止线程传递给pthread_exit()的值在value_ptr引用的位置可用。 当pthread_join()成功返回时,目标线程已终止。 指定同一目标线程的多个同时调用pthread_join()的结果是未定义的。 如果取消调用pthread_join()的线程,则不会分离目标线程。

但是,您可以通过使用轻量级循环来实现相同的目的,这将阻止exe退出。 在Glib中,这是通过创建GMainLoop来实现的,在Gtk +中你可以使用gtk_main 。 完成线程后,您必须退出主循环或调用gtk_exit

或者,您可以使用套接字,管道和选择系统调用的组合创建自己的等待function,但这不是必需的,可以视为练习练习。