POSIX pthread编程

我必须编写一个multithreading(比如两个线程)程序,其中每个线程执行不同的任务。 此外,这些线程一旦启动就必须在后台无限运行。 这就是我所做的。 有人可以给我一些反馈,如果方法是好的,如果你看到一些问题。 另外,我想知道如果用Ctrl + C终止执行,如何以系统的方式关闭线程。

main函数创建两个线程,让它们无限运行,如下所示。

这是骨架:

void *func1(); void *func2(); int main(int argc, char *argv[]) { pthread_t th1,th2; pthread_create(&th1, NULL, func1, NULL); pthread_create(&th2, NULL, func2, NULL); fflush (stdout); for(;;){ } exit(0); //never reached } void *func1() { while(1){ //do something } } void *func2() { while(1){ //do something } } 

谢谢。

使用答案中的输入编辑代码:我是否正确退出线程?

 #include  /* exit() */ #include  /* standard in and output*/ #include  #include  #include  #include  #include  #include  #include  sem_t end; void *func1(); void *func2(); void ThreadTermHandler(int signo){ if (signo == SIGINT) { printf("Ctrl+C detected !!! \n"); sem_post(&end); } } void *func1() { int value; for(;;){ sem_getvalue(&end, &value); while(!value){ printf("in thread 1 \n"); } } return 0; } void *func2() { int value; for(;;){ sem_getvalue(&end, &value); while(!value){ printf("value = %d\n", value); } } return 0; } int main(int argc, char *argv[]) { sem_init(&end, 0, 0); pthread_t th1,th2; int value = -2; pthread_create(&th1, NULL, func1, NULL); pthread_create(&th2, NULL, func2, NULL); struct sigaction sa; sigemptyset(&sa.sa_mask); sa.sa_flags = SA_SIGINFO; sa.sa_sigaction = ThreadTermHandler; // Establish a handler to catch CTRL+c and use it for exiting. if (sigaction(SIGINT, &sa, NULL) == -1) { perror("sigaction for Thread Termination failed"); exit( EXIT_FAILURE ); } /* Wait for SIGINT. */ while (sem_wait(&end)!=0){} //{ printf("Terminating Threads.. \n"); sem_post(&end); sem_getvalue(&end, &value); /* SIGINT received, cancel threads. */ pthread_cancel(th1); pthread_cancel(th2); /* Join threads. */ pthread_join(th1, NULL); pthread_join(th2, NULL); //} exit(0); } 

线程终止主要有两种方法。

  • 使用取消点。 线程将在请求取消时终止并且到达取消点,从而以受控方式结束执行;
  • 使用信号。 让线程安装一个信号处理程序,它提供终止机制(设置一个标志并对EINTR作出反应)。

两种方法都有警告。 有关更多详细信息,请参阅Pthread库中的Kill Thread 。

在您的情况下,似乎是一个使用取消点的好机会。 我将使用一个评论的例子。 为清楚起见,省略了错误检查。

 #define _POSIX_C_SOURCE 200809L #include  #include  #include  #include  #include  void sigint(int signo) { (void)signo; } void *thread(void *argument) { (void)argument; for (;;) { // Do something useful. printf("Thread %u running.\n", *(unsigned int*)argument); // sleep() is a cancellation point in this example. sleep(1); } return NULL; } int main(void) { // Block the SIGINT signal. The threads will inherit the signal mask. // This will avoid them catching SIGINT instead of this thread. sigset_t sigset, oldset; sigemptyset(&sigset); sigaddset(&sigset, SIGINT); pthread_sigmask(SIG_BLOCK, &sigset, &oldset); // Spawn the two threads. pthread_t thread1, thread2; pthread_create(&thread1, NULL, thread, &(unsigned int){1}); pthread_create(&thread2, NULL, thread, &(unsigned int){2}); // Install the signal handler for SIGINT. struct sigaction s; s.sa_handler = sigint; sigemptyset(&s.sa_mask); s.sa_flags = 0; sigaction(SIGINT, &s, NULL); // Restore the old signal mask only for this thread. pthread_sigmask(SIG_SETMASK, &oldset, NULL); // Wait for SIGINT to arrive. pause(); // Cancel both threads. pthread_cancel(thread1); pthread_cancel(thread2); // Join both threads. pthread_join(thread1, NULL); pthread_join(thread2, NULL); // Done. puts("Terminated."); return EXIT_SUCCESS; } 

阻塞/解除阻塞信号的需求是,如果您将SIGINT发送到进程,任何线程都可以捕获它。 您在生成线程之前这样做是为了避免让它们自己执行并需要与父级同步。 创建线程后,还原掩码并安装处理程序。

如果线程分配了大量资源,取消点可能会很棘手; 在这种情况下,你将不得不使用pthread_cleanup_push()pthread_cleanup_pop() ,这是一个烂摊子。 但如果使用得当,这种方法是可行的,相当优雅。

答案很大程度上取决于用户按Ctrl C时要执行的操作。

如果您的工作线程没有修改退出时需要保存的数据,则无需执行任何操作。 SIGINT的默认操作是终止进程 ,包括构成进程的所有线程。

但是,如果您的线程确实需要执行清理,那么您还有一些工作要做。 您需要考虑两个单独的问题:

  • 如何处理信号并将消息传递给需要终止的线程。
  • 线程如何接收和处理终止请求。

首先,信号处理程序是一种痛苦。 除非你非常小心,否则你必须假设从信号处理程序调用大多数库函数是不合法的。 幸运的是, sem_post被指定为异步信号安全,并且可以完美地满足您的要求:

  1. 在程序开始时,使用sem_init(&exit_sem, 0, 0);初始化信号量sem_init(&exit_sem, 0, 0);
  2. 为执行sem_post(&exit_sem); SIGINT (以及您想要处理的任何其他终止信号,如SIGTERM )安装信号处理程序sem_post(&exit_sem); 并返回。
  3. 替换for(;;); 在主线程中使用while (sem_wait(&exit_sem)!=0)
  4. sem_wait成功之后,主线程应该通知所有其他线程它们应该退出,然后等待它们全部退出。

上面也可以使用信号掩码和sigwaitinfo在没有信号量的情况下完成,但我更喜欢信号量方法,因为它不需要你学习很多复杂的信号语义。

现在,有几种方法可以处理通知工作线程是时候退出了。 我看到的一些选项:

  1. 让他们定期检查sem_getvalue(&exit_sem)并清理并退出,如果它返回非零值。 但是请注意,如果线程被无限期阻塞,例如在调用readwrite中,这将不起作用。
  2. 使用pthread_cancel ,并小心地将取消处理程序( pthread_cleanup_push )放在所有地方。
  3. 使用pthread_cancel ,但也可以使用pthread_setcancelstate在大多数代码中禁用取消,并且只在您要执行阻塞IO操作时重新启用它。 这样,您只需将清理处理程序放在启用取消的位置。
  4. 学习高级信号语义,并设置一个额外的信号和中断信号处理程序,你通过pthread_kill发送到所有线程,这将导致阻塞系统调用返回EINTR错误。 然后您的线程可以对此进行操作并通过一系列故障退出正常的C方式,一直返回start函数。

我不建议初学者使用方法4 ,因为很难做到正确,但对于高级C程序员来说,它可能是最好的,因为它允许您使用现有的C语言来通过返回值而不是“例外”来报告exception条件。

另请注意,使用pthread_cancel ,如果不调用任何其他取消点函数,则需要定期调用pthread_testcancel 。 否则,取消请求将永远不会被采取行动。

这是一个坏主意:

 for(;;){ } 

因为你的主线程会执行不必​​要的CPU指令。

如果您需要在主线程中等待,请使用pthread_join作为此问题中的答案: C程序中的多个线程

你做了什么工作,我发现它没有明显的问题(除了你忽略了pthread_create的返回值)。 不幸的是, 停止线程比你想象的更复杂 。 您想要使用信号的事实是另一个复杂因素。 这是你能做的。

  • 在“children”线程中,使用pthread_sigmask来阻止信号
  • 在主线程中,使用sigsuspend等待信号
  • 收到信号后,取消( pthread_cancel )子线程

你的主线程看起来像这样:

 /* Wait for SIGINT. */ sigsuspend(&mask); /* SIGINT received, cancel threads. */ pthread_cancel(th1); pthread_cancel(th2); /* Join threads. */ pthread_join(th1, NULL); pthread_join(th2, NULL); 

显然,您应该阅读有关pthread_cancel和取消点的更多信息。 您还可以安装清理处理程序 。 当然,检查每个返回值。

看着你更新的编码,它看起来仍然不正确。

信号处理必须只在一个线程中完成。 针对进程(例如SIGINT)的信号被传递到没有阻止该信号的任何线程。 换句话说,不能保证给定三个线程它将成为接收SIGINT的主线程。 在multithreading程序中,最佳做法是在创建任何线程之前阻塞所有信号,并且一旦创建了所有线程,只取消阻塞主线程中的信号(通常它是处理信号的最佳位置的主线程) 。 有关更多信息 ,请参阅multithreading过程中的 信号概念和信号 。

最好避免使用pthread_cancel ,没有理由使用它。 要停止线程,你应该以某种方式告诉他们他们应该终止并等到他们自愿终止。 通常,线程将具有某种事件循环,因此向另一个线程发送事件应该相对简单。

调用pthread_cancel并在线程函数中使用pthread_cleanup_push来潜在地清理线程动态分配的数据或执行线程停止之前所需的任何终止任务不是更容易。

所以这个想法是:

  1. 编写代码来处理信号
  2. 当你执行ctrl + c …调用处理函数
  3. 此函数取消线程
  4. 创建的每个线程使用pthread_cleanup_push设置线程清理function
  5. 当取消胎面时,调用pthread_cleanup_push的函数
  6. 在退出之前加入所有线程

这似乎是一个简单而自然的解决方案。

 static void cleanup_handler(void *arg) { printf("Called clean-up handler\n"); } static void *threadFunc(void *data) { ThreadData *td = (ThreadData*)(data); pthread_cleanup_push(cleanup_handler, (void*)something); while (1) { pthread_testcancel(); /* A cancellation point */ ... } pthread_cleanup_pop(cleanup_pop_arg); return NULL; } 

你不需要主要的foor循环。 一个th1->join(); th2->join(); th1->join(); th2->join(); 因为线程永远不会结束,所以作为等待条件就足够了。

要停止线程,您可以使用全局共享var,例如bool stop = false; ,然后当捕获信号时(Ctrl + Z是UNIX中的信号),设置stop = true中止线程,因为你正在等待join() ,主程序也将退出。

 void *func1(){ while(!stop){ //do something } }