线程创建期间程序执行的流程

问题描述 投票:8回答:4

我是新手。

我编写了一个示例程序来创建一个线程。

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
#include<string.h>
#include<pthread.h>


void * func(void * temp)
{
    printf("inside function\n");
    return NULL;
}

int main()
{
    pthread_t pt1;
    printf("creating thread\n");
    pthread_create(&pt1,NULL,&func,NULL);
    printf("inside main created thread\n");

return 0;
}

编译后,答案是:

creating thread
inside main created thread
inside function
inside function

我理解答案可能会有所不同,因为在执行func中的return 0;之前可能会调用printf。但是如何在解决方案中,inside function被打印两次?

在第一次运行时使用gcc -o temp thread1.c -lpthread进行编译:

creating thread
inside main created thread

第二轮:

creating thread
inside main created thread
inside function
inside function

在使用gcc -pthread -o temp thread1.c编译时首次运行:

creating thread
inside main created thread
inside function
inside function

我观察到了这种行为

gcc version: 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
Kernel release:2.6.32-24-generic
glib version:2.11.1
c multithreading pthreads
4个回答
3
投票

我在gcc版本4.6.3(Ubuntu / Linaro 4.6.3-1ubuntu5),带有O2标志的glib版本2.15上观察到了这个问题。如果没有任何优化标志,则不会出现此问题。

为什么输出很奇怪 C语言规范不引用任何特定的编译器,操作系统或CPU。它引用了一个抽象机器,它是实际系统的概括。这个抽象机器(至少达到C99规格)是单线程的。因此,默认情况下,标准库(包括printf)不需要是线程安全的。如果您在线程中使用标准库函数(使用某些库,例如posix libpthread),则您需要在访问非参数标准库函数之前添加同步(互斥,信号量,condvar等)。如果不这样做,可能会不时出现令人惊讶的结果,您应该自担风险。

在我可以重现这个问题的环境中的一些分析 分析为两个版本的标志生成的程序集,我找不到任何显着的差异(一点明显,printfs被转换为puts

看看puts的来源

int
_IO_puts (str)
     const char *str;
{
  int result = EOF;
  _IO_size_t len = strlen (str);
  _IO_acquire_lock (_IO_stdout);

  if ((_IO_vtable_offset (_IO_stdout) != 0
       || _IO_fwide (_IO_stdout, -1) == -1)
      && _IO_sputn (_IO_stdout, str, len) == len
      && _IO_putc_unlocked ('\n', _IO_stdout) != EOF)
    result = MIN (INT_MAX, len + 1);

  _IO_release_lock (_IO_stdout);
  return result;
}

#ifdef weak_alias
weak_alias (_IO_puts, puts)
#endif

似乎问题出在_IO_putc_unlocked('\n', _IO_stdout)。这可以刷新流并且可以在更新流状态之前被杀死。

学习多线程编码 当主线程返回时,它终止整个进程。这包括所有其他线程。因此,请指示所有子线程退出(或使用pthread_kill)并使主线程退出pthread_exit或使用pthread_join


1
投票

通常,您不能假设printf和相关的内存结构是线程安全的。这取决于stdio库的实现方式。特别是,当线程和进程终止时,可能会发生故障,因为运行时库通常会在退出之前刷新输出缓冲区。我已经看到过这种行为,解决方案通常是一个互斥或信号量来保护输出操作(更准确地说,是为了保护对FILE对象的访问)。


1
投票

首先,据我所知,使用“-pthread”进行编译相当于使用“-D_REENTRANT -lpthread”进行编译,因此这是唯一的区别。请注意,printf等不可重入,因为它们在全局缓冲区上运行。

话虽如此,遗憾的是我无法重新创建问题的有趣部分(你的printf在线程目标函数中似乎被调用了两次)。每种编译方法(-lpthread和-pthread)都给出了相同的结果:我从main内部获取打印,但没有从线程目标中打印(正如您在第一次运行时看到的那样)。我认为这只是一个时间问题,线程目标不是在主要退出之前“绕过”打印。实际上,在从main返回之前只是睡了1/100秒就可以获得线程目标函数的打印。尝试一下,让我们知道你看到了什么:

#include<stdio.h>
#include<stdlib.h>
#include<limits.h>
#include<string.h>
#include<pthread.h>
#include <unistd.h>

void * func(void * temp)
{
    printf("inside function\n");
    return NULL;
}

int main()
{
    pthread_t pt1;
    printf("creating thread\n");
    pthread_create(&pt1,NULL,&func,NULL);
    printf("inside main created thread\n");

    /* ZzzzzZZZ... */
    usleep(10000);

    return 0;
}

我玩了延迟时间,即使是1/1000000秒:usleep(1);我仍然得到所有我期望的printfs。随着睡眠延迟减少,打印更有可能发生故障,我希望看到。

因此关于多个打印:在我之前指出的许多内容中,printf等在全局结构上运行,并且不是可重入的。如果你在printf之后刷新stdout,我有兴趣看看你的输出,所有这些都受到互斥锁的保护:

#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>

static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void * func(void * temp)
{
    pthread_mutex_lock(&mutex);
    printf("inside function\n");
    fflush(stdout);
    pthread_mutex_unlock(&mutex);

    return NULL;
}

int main()
{
    pthread_t pt1;

    pthread_mutex_lock(&mutex);
    printf("creating thread\n");
    fflush(stdout);
    pthread_mutex_unlock(&mutex);

    pthread_create(&pt1,NULL,&func,NULL);

    pthread_mutex_lock(&mutex);
    printf("inside main created thread\n");
    fflush(stdout);
    pthread_mutex_unlock(&mutex);

    usleep(10000);

    return 0;
}

编辑

对不起,当我建议使用上面的fflush()时,我并不是100%清楚。我相信问题是你在字符被推到屏幕的时间和刷新缓冲区之间被打断了。当缓冲区实际上被真正刷新时,你已经有效地将你的字符串推了两次。


0
投票

我无法评论,但有several duplicate questions提供更多信息。 glibc bug可能是意外行为的原因,正如其他答案所述,pthread_exitpthread_join是建议的解决方法。

© www.soinside.com 2019 - 2024. All rights reserved.