【问题标题】:Cancelling a pthread and printf behaviour取消 pthread 和 printf 行为
【发布时间】:2013-06-17 00:25:27
【问题描述】:

这是一个pthread取消的代码示例:

#include <stdio.h>
#include <unistd.h>
#include <pthread.h>

void *my_routine(void *arg) {
  int i;
  for (i = 0; i < 10; i++) {
    printf("%d\n", i);
  }
  return NULL;
}

int main(void) {
  pthread_t thread;
  if (pthread_create(&thread, NULL, my_routine, NULL)) {
    fprintf(stderr, "Cannot create pthread\n");
    return 1;
  }
  usleep(20);
  pthread_cancel(thread);
  pthread_join(thread, NULL);
  //fflush(stdout);
  sleep(1);
  return 0;
}

我编译:

gcc -pthread -Wall threadtest.c -o threadtest

执行时,有时会在sleep(1)之后打印一个额外的数字。

有时这个数字是重复的:

0
1
2
3
4
4  // printed after sleep(1)

有时不是:

0
1
2
3
4
5  // printed after sleep(1)

如果我在sleep(1) 之前fflush(stdout),则立即打印附加号码。

取消线程时如何避免这种奇怪的printf 行为?

谢谢。

【问题讨论】:

    标签: c pthreads printf cancellation


    【解决方案1】:

    您可以在执行printf() 时禁用取消并添加一个明确的取消点:

    int cancel_state = 0;
    pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &cancel_state);
    printf("%d\n", i);
    pthread_setcancelstate(cancel_state);
    pthread_testcancel();
    

    (为了便于阅读,省略了错误检查)

    【讨论】:

    • 是的,它“有效”(我将NULL 作为pthread_setcancelstate 的第二个参数),但这不是解决方案。我不敢相信,虽然printf 是一个取消点,但在取消时它的行为并不正确。
    • @rom1v: printf() 使用共享资源,即stdout。很可能该线程在stdout 的输出缓冲区中留下了一些东西,因为它无法完成它的工作,因为它被取消了。然后,这个剩余部分由fflush() 上的主线程打印出来或退出。应保护对共享资源的访问。
    • 应该受什么保护?我尝试使用flockfile/funlockfile(使用pthread_cleanup_push/pthread_cleanup_pop),但没有成功。互斥锁也无济于事:这不是多线程访问问题,这似乎是一个取消问题,printf 没有正确处理(它不应该在工作过程中接受取消)。
    • @rom1v:在这种情况下,“受保护”以防止不完整的使用。 “...它不应该在工作中接受取消。...”这就是我的方法正在处理的问题。
    • @alk 我认为您无法建立这种联系。标准列出了一组作为取消点的函数,一组可能是取消点的函数,并禁止标准中的其他函数作为取消点。而且我不认为让 EINTR 允许让东西处于不一致的状态。所以例如printf 要么是一个取消点,如果是这样,它的行为必须是可预测的,或者它不是一个取消点,因此即使取消请求处于未决状态,它也会正常完成。
    【解决方案2】:

    使用 pthread_cancel 时,您应该更加注意线程处理程序。

    pthread_cancel 的手册页对此进行了很多解释。

    man pthread_cancel

    查看手册页中的示例程序

    当线程处理程序启动时,您必须在 pthread_setcancelstate 系统调用的帮助下进行 PTHREAD_CANCEL_DISABLE,当处理程序遇到取消点时设置 PTHREAD_CANCEL_ENABLE。

    在PTHREAD_CANCELED宏的帮助下主要检查pthread_cancel是否成功。

    #include <stdio.h>
    #include <unistd.h>
    #include <pthread.h>
    void *my_routine(void *arg) {
      int i;
      //pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
      for (i = 0; i < 10; i++) {
        sleep(1);
        printf("%d\n", i);
      }
      //pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
      sleep(60);
      return NULL;
    }
    int main(void) 
    {
      pthread_t thread;
      if (pthread_create(&thread, NULL, my_routine, NULL)) {
        fprintf(stderr, "Cannot create pthread\n");
        return 1;
      }
    usleep(20);
    pthread_cancel(thread);
    pthread_join(thread, NULL);
    //fflush(stdout);
    sleep(1);
    return 0;
    }
    
    1. 最初编译并运行上述程序,注意输出。它可能不会显示任何内容并终止程序,因为在线程创建后线程处理程序将等待 1 秒并休眠,届时父线程将取消线程并完成进程。

    2. 然后取消上面程序中两个pthread_setcancelstate()的注释,现在编译运行程序。现在它会显示从 1 到 9。因为线程设置为 PTHREAD_CANCEL_DISABLE,所以父 pthread_cancel 不会取消并等待 PTHREAD_CANCEL_ENABLE。当线程处理程序遇到 PTHREAD_CANCEL_ENABLE 时父线程将取消线程。

    3. 当您记下输出时。处理程序不会保持睡眠(60),因为在此之前父级将取消线程。

    【讨论】:

    • 好的,但我的意思是printf 一个取消点,但我们不应该依赖它。我认为这是合乎逻辑的,对于具有取消类型 PTHREAD_CANCEL_DEFERRED 的线程,printf 中的取消点将避免让垃圾进入标准输出......
    猜你喜欢
    • 2021-01-30
    • 2015-01-22
    • 2011-05-12
    • 1970-01-01
    • 2012-04-03
    • 2020-08-04
    • 2014-06-06
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多