我必须编写一个多线程(比方说2个线程)程序,其中每个线程执行不同的任务。此外,这些线程一旦启动就必须在后台无限运行。这就是我所做的。有人可以给我一些反馈,如果方法是好的,如果你看到一些问题。另外,我想知道如果用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 <stdlib.h> /* exit() */
#include <stdio.h> /* standard in and output*/
#include <pthread.h>
#include <unistd.h>
#include <time.h>
#include <sys/time.h>
#include <sys/types.h>
#include <signal.h>
#include <semaphore.h>
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);
}
答案 0 :(得分:13)
线程终止主要有两种方法。
EINTR
作出反应)。这两种方法都有警告。有关详细信息,请参阅Kill Thread in Pthread Library。
在您的情况下,这似乎是一个使用取消点的好机会。我将使用一个评论的例子。为清楚起见,省略了错误检查。
#define _POSIX_C_SOURCE 200809L
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
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()
,这是一团糟。但如果使用得当,这种方法是可行的,相当优雅。
答案 1 :(得分:7)
当用户按 Ctrl C 时,答案很大程度上取决于您想要做什么。
如果您的工作线程没有修改退出时需要保存的数据,则无需执行任何操作。 SIGINT
的默认操作是终止进程,其中包括构成该进程的所有线程。
但是,如果您的线程确实需要执行清理,那么您还有一些工作要做。您需要考虑两个单独的问题:
首先,信号处理程序很痛苦。除非你非常小心,否则你必须假设从信号处理程序调用大多数库函数是不合法的。幸运的是,sem_post
被指定为异步信号安全,并且可以完美地满足您的要求:
sem_init(&exit_sem, 0, 0);
SIGINT
并返回的SIGTERM
(以及您要处理的任何其他终止信号,如sem_post(&exit_sem);
)安装信号处理程序。for(;;);
替换为while (sem_wait(&exit_sem)!=0)
。sem_wait
成功后,主线程应该通知所有其他线程它们应该退出,然后等待它们全部退出。上面也可以在没有使用信号掩码和sigwaitinfo
的信号量的情况下完成,但我更喜欢信号量方法,因为它不需要你学习很多复杂的信号语义。
现在,有几种方法可以处理通知工作线程是时候退出了。我看到的一些选项:
sem_getvalue(&exit_sem)
并清理并退出,如果它返回非零值。但请注意,如果线程被无限期阻止,例如调用read
或write
,则无效。pthread_cancel
,并小心地将取消处理程序(pthread_cleanup_push
)放在所有地方。pthread_cancel
,但也可以使用pthread_setcancelstate
在大多数代码中禁用取消,并且只在您要执行阻止IO操作时重新启用它。这样,您只需将清理处理程序放在启用取消的位置。pthread_kill
发送给所有线程,这将导致阻塞系统调用返回EINTR
错误。然后你的线程可以对此采取行动,并通过一系列故障退出正常的C方式,一直返回start函数。我不建议初学者使用方法4 ,因为很难做到正确,但对于高级C程序员来说,它可能是最好的,因为它允许您使用现有的C语言来报告异常情况通过返回值而不是“例外”。
另请注意,对于pthread_cancel
,如果您未调用cancellation points的任何其他功能,则需要定期致电pthread_testcancel
。否则,取消请求将永远不会被采取行动。
答案 2 :(得分:3)
这是个坏主意:
for(;;){
}
因为您的主线程将执行不必要的CPU指令。
如果您需要在主要帖子中等待,请按照此问题中的回答使用pthread_join
:Multiple threads in C program
答案 3 :(得分:3)
你做了什么工作,我发现它没有明显的问题(除了你忽略了pthread_create
的返回值)。不幸的是,停止线程比你想象的更多涉及。您想要使用信号的事实是另一个复杂因素。这就是你能做的。
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
和取消点的更多信息。您还可以安装cleanup handler。当然,检查每个返回值。
答案 4 :(得分:1)
查看更新的编码,但看起来仍然不正确。
信号处理必须只在一个线程中完成。针对进程(例如SIGINT)的信号将传递给没有阻止该信号的任何线程。换句话说,不能保证给定三个线程它将成为接收SIGINT的主线程。在多线程程序中,最佳做法是在创建任何线程之前阻塞所有信号,并且一旦创建了所有线程,只取消阻塞主线程中的信号(通常它是处理信号的最佳位置的主线程) 。有关详情,请参阅Signal Concepts和Signalling in a Multi-Threaded Process。
最好避免使用 pthread_cancel
,没有理由使用它。要停止线程,你应该以某种方式告诉他们他们应该终止并等到他们自愿终止。通常情况下,线程会有某种事件循环,因此向其他线程发送事件应该相对简单。
答案 5 :(得分:1)
调用pthread_cancel并在线程函数中使用pthread_cleanup_push以便潜在地清理线程动态分配的数据或执行线程停止前所需的任何终止任务更容易。
所以想法是:
这似乎是一个简单而自然的解决方案。
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;
}
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;
}
答案 6 :(得分:0)
你不需要主要的foor循环。 th1->join(); th2->join();
足以作为等待条件,因为线程永远不会结束。
要停止线程,您可以使用全局共享var,如bool stop = false;
,然后在捕获信号时(Ctrl + Z是UNIX中的信号),设置stop = true
中止线程,因为你是等待join()
主程序也将退出。
例如
void *func1(){
while(!stop){
//do something
}
}