我使用pthread和printf时的奇怪输出

时间:2012-11-02 06:13:55

标签: c pthreads printf

我使用pthread编写程序。

环境:Windows 7,CYGWIN_NT-6.1 i686 Cygwin,gcc(GCC)4.5.3

源代码

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

void *th_func(void *p)
{
    int iLoop = 0;

    for(iLoop = 0;iLoop<100;iLoop++)
    {
        printf("Thread Thread Thread Thread\n");
    }

    return;
}

int main()
{
    int iLoop = 0;
    pthread_t QueThread;

    printf("Main : Start Main\n");

    printf("Main : Start Create Thread\n");
    pthread_create(&QueThread,NULL,th_func,NULL);
    printf("Main : End Create Thread\n");

    for(iLoop = 0;iLoop<100;iLoop++)
    {
        printf("Main Main Main Main\n");
    }

    pthread_join(QueThread,NULL);

    printf("Main : End Main\n");

    printf("---------------\n");

    return 0;
}

当我编译源代码时,没有警告或错误,但它的输出很奇怪。

它的一部分输出

Main : Start Main
Main : Start Create Thread
Thread Thread Thread ThreThread Thread Thread Thread
Main Main Main Main
Thread Thread Thread Thread
Main Main Main Main

我想知道这种现象的原因。

在此输出中,Main : End Create Thread未完全打印。在第3行,\n末尾的换行符"Thread Thread Thread Thread\n"消失。

每个人的输出都是这样的吗?它不会每次都发生,但有时会发生。

如果我使用互斥锁安全地呼叫printf,那么奇怪的输出似乎就会停止。

POSIX称printf是线程安全的,根据Cygwin.com,cygwin提供了posix风格的API。但是,有意想不到的输出。

printf真的是线程安全的吗?

我在Linux(Ubuntu)中执行了相同的程序100次,并且没有出现此输出。

另外,我还没有理解输出中某些词语消失的原因。

5 个答案:

答案 0 :(得分:4)

POSIX标准具有putc_unlocked()等功能,其中评论说:

  

分别命名为getc()getchar()putc()的函数putchar()getc_unlocked()getchar_unlocked()putc_unlocked()的版本,并且应提供putchar_unlocked(),它们在功能上等同于原始版本,但不要求它们以线程安全的方式实现。它们只能安全地在受flockfile()(或ftrylockfile())和funlockfile()保护的范围内使用。当且仅当在调用线程拥有(FILE *)对象时调用它们时,这些函数可以安全地用在多线程程序中,就像成功调用flockfile()ftrylockfile()函数。

这清楚地表明单字符I / O的低级函数通常是线程安全的。但是,它还表明粒度级别是单个字符输出操作。 printf()的规范说:

  

fprintf()printf()生成的字符将被打印,就像调用了fputc()一样。

对于putc(),它说:

  

putc()函数应该等同于fputc(),除非它作为宏实现,它可以多次评估流,因此参数永远不应该是带有副作用的表达式。

fputc()的页面没有提及有关线程安全的任何内容,因此您必须在其他地方查找该信息。

另一个section描述了线程并说:

  

本卷POSIX.1-2008定义的所有函数都是线程安全的,但以下函数不必是线程安全的。

以下列表包含*_unlocked()个功能。

因此,printf()fputc()必须是线程安全的,但printf()的写入“就像'fputc()一样,所以输出的交错线程之间可能处于字符级别,这或多或少与您看到的一致。如果您要拨打printf()非交错的电话,则需要使用flockfile()funlockfile()来调用stdout的线程所有权{{1}执行。同样适用于printf()。您可以非常轻松地编写fprintf()函数来实现此结果:

fprintf_locked()

如果您愿意,可以在那里插入int fprintf_locked(FILE *fp, const char *format, ...) { flockfile(fp); va_list args; va_start(args, format); int rc = vfprintf(fp, format, args); va_end(args); funlockfile(fp); return rc; } 。您还可以拥有fflush(fp)并具有上述函数来执行锁定,格式化,(刷新)和解锁操作。这可能是我编写代码的方式,相信编译器可以内联代码,如果这是合适的和可行的。使用vfprintf_locked()支持版本同样非常简单。

请注意flockfile()Michael Burr引用的answer引用的{{3}}的POSIX规范片段:

  

所有引用(stdout)对象的函数,除了名称以FILE *结尾的对象外,其行为应该像在内部使用_unlockedflockfile()一样获得所有权这些(funlockfile())个对象。

FILE *周围的奇数括号外,这些行影响所有其他标准I / O函数,但您必须知道这些行存在于一个不太常用的手册页中。因此,我的FILE *函数应该是不必要的。如果你发现fprintf_locked()的异常实现没有锁定文件,那么可以使用fprintf()函数,但它应该只能在抗议下完成 - 无论如何库应该为你做

答案 1 :(得分:3)

这看起来可能是Cygwin中的一个错误,或者可能是错误配置的错误。这里的几个答案表明'线程安全'只承诺该函数不会对程序造成损害,并且线程安全并不一定意味着函数是“原子的”。但是,据我所知,POSIX没有正式定义'线程安全'(如果有人指向这样的定义,请在评论中发布)。

但是,POSIX不仅指定printf()是线程安全的,POSIX also specifies

  

所有引用(FILE *)对象的函数都应该在内部使用flockfile()和funlockfile()来获取这些(FILE *)对象的所有权。

由于printf()隐式引用stdout FILE*对象,所有printf()调用应该相互之间是原子的(以及使用{{1}的任何其他函数})。

请注意,在其他系统上可能不是这样,但根据我的经验,它确实适用于许多多线程系统。

答案 2 :(得分:0)

仅仅因为函数是线程安全的,它并不意味着它是原子的。

在您的情况下,如果您想确保输出不会交错,则需要使用互斥锁来确保一次只有一个线程调用printf

答案 3 :(得分:0)

出于某种原因,线程的行为与此类似。如果线程一个接一个地执行而不是“同时”(以交错的方式),那么这种“并发”就没有意义了。当您使用互斥锁时,线程将根据您的意图被阻止,并生成预期的输出。

此外,您在返回return;的函数中编写void *并且这是未定义的行为,因此在运行程序时可能会发生任何事情。

答案 4 :(得分:0)

我将以简单的方式提供这两个尝试访问资源的线程。并且没有优先级检查或类似互斥体的任何类型。从理论上讲,没有互斥或优先级的线程会随机分配资源。尝试创建两个线程,一个线程打印是,另一个打印否。你会发现这种不寻常的行为。还记得在这种情况下不同线程的运行时间是不同的。如果你尝试使用一个线程将相同的东西写入文件和其他人写入控制台。你不会遇到这样的问题。希望有所帮助......