POSIX pthread编程

时间:2011-07-08 08:17:58

标签: c pthreads posix

我必须编写一个多线程(比方说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);
}

7 个答案:

答案 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被指定为异步信号安全,并且可以完美地满足您的要求:

  1. 在程序开头,使用sem_init(&exit_sem, 0, 0);
  2. 初始化信号量
  3. 为执行SIGINT并返回的SIGTERM(以及您要处理的任何其他终止信号,如sem_post(&exit_sem);)安装信号处理程序。
  4. 将主线程中的for(;;);替换为while (sem_wait(&exit_sem)!=0)
  5. sem_wait成功后,主线程应该通知所有其他线程它们应该退出,然后等待它们全部退出。
  6. 上面也可以在没有使用信号掩码和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函数。
    5. 我不建议初学者使用方法4 ,因为很难做到正确,但对于高级C程序员来说,它可能是最好的,因为它允许您使用现有的C语言来报告异常情况通过返回值而不是“例外”。

      另请注意,对于pthread_cancel,如果您未调用cancellation points的任何其他功能,则需要定期致电pthread_testcancel。否则,取消请求将永远不会被采取行动。

答案 2 :(得分:3)

这是个坏主意:

for(;;){
}

因为您的主线程将执行不必要的CPU指令。

如果您需要在主要帖子中等待,请按照此问题中的回答使用pthread_joinMultiple 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 ConceptsSignalling in a Multi-Threaded Process

最好避免使用

pthread_cancel,没有理由使用它。要停止线程,你应该以某种方式告诉他们他们应该终止并等到他们自愿终止。通常情况下,线程会有某种事件循环,因此向其他线程发送事件应该相对简单。

答案 5 :(得分:1)

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

所以想法是:

  1. 编写处理信号的代码
  2. 当你执行ctrl + c ...处理函数被称为
  3. 此函数取消线程
  4. 创建的每个线程使用pthread_cleanup_push
  5. 设置线程清理功能
  6. 当取消胎面时,调用pthread_cleanup_push的功能
  7. 在退出前加入所有主题
  8. 这似乎是一个简单而自然的解决方案。

    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
  }
}