2012-11-02 33 views
5

我使用pthread编写一个程序。奇怪的输出,当我使用pthread和printf

环境: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行,在"Thread Thread Thread Thread\n"末尾的换行\n消失。

大家的输出是这样吗?它不会每次都发生,但会在某个时候发生。

如果我使用互斥锁安全地调用printf,奇怪的输出似乎停止。

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

printf真的线程安全吗?

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

此外,我还没有理解为什么输出中的一些单词消失的原因。

+2

您只看到两个线程输出交错。 – CCoder

+2

线程安全并不意味着它是原子。 – Barmar

+0

这个问题与确定的重复无关。这个任务具体是关于POSIX是否指定'printf()'在进程中的线程之间应该是原子的。所标识的重复是关于更一般的线程调度和竞争条件。 –

回答

3

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

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

引用(FILE *)对象应表现为,如果他们使用flockfile()和funlockfile(所有功能)内部以获取这些(FILE *)对象的所有权。

由于printf()隐含引用stdoutFILE*对象,所有printf()调用应该是原子相对于彼此(和使用stdout任何其它功能)。

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

+0

“线程安全”的定义是在[第3.399](http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap03.html#tag_03_399)。关于文件锁,由于'printf()'是一系列'putc()'调用,只有后者实际引用'FILE *'并且需要获得所有权。 – Barmar

+0

感谢指向'线程安全'定义的指针(但我必须说,我认为它留下了很多想要的)。我也不认为这是引用'(FILE *)'对象的正确解释。我知道这只是对权威的一种诉求,但是这里是David Butenhof(“使用POSIX线程编程”一书的作者)的一篇文章,他声明'printf()'必须是原子的:http://newsgroups.derkeiler.com/Archive /Comp/comp.programming.threads/2009-06/msg00058.html –

+0

我已经开始认为它是Cygwin中的错误。当我在Linux中执行程序时,奇怪的输出没有发生。 –

0

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

就你而言,如果你想确保你的输出不会被交错,你需要使用一个互斥来确保一次只有一个线程调用printf

+0

年,互斥锁阻止输出交错。当我在Linux中编译和执行程序时,输出不会发生。 –

0

线程的行为是有原因的。如果一个接一个地执行线程,而不是'同时'(以交错的方式),那么这种'并发性'就没有意义。当你使用互斥锁时,线程将根据你的意图被阻塞,并且它们会产生预期的输出。

此外,你在一个返回void *的函数中写入return;,这是未定义的行为,因此在运行程序时会发生任何事情。

+0

我不知道返回不应该写在void函数中!谢谢!我删除了返回; ,但是会出现相同的现象。你的第一句话是否意味着在交错和并发之间不一样? –

0

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

+0

当我执行您的Yes/No程序时,随机显示Yes或No! –

4

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

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

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

的字符印刷仿佛fputc()被调用。

而对于putc(),它说:

putc()功能应相当于fputc(),但如果它是作为一个宏来实现它可以评估流不止一次,这样的参数应该永远成为一个带有副作用的表达。

fputc()的页面没有提到有关线程安全的任何信息,因此您必须在其他位置查找该信息。

另一个section介绍线程和说:

通过此卷POSIX.1-2008定义的所有功能应是线程安全的,不同之处在于以下功能不必是线程安全的。

而下面的列表包括*_unlocked()函数。

所以,printf()fputc()必须是线程安全的,但书面printf()完成“仿佛”由fputc(),所以线程之间输出的交织可能是在人物等级,这与你所看到的大致一致。如果您想拨打printf()非交错电话,则需要使用flockfile()funlockfile()呼叫,以便在执行printf()时让线程拥有stdout。对于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()并具有上述函数来执行锁定,格式化,(刷新)和解锁操作。这可能是我编写代码的过程,如果这是合适且可行的,相信编译器将代码嵌入代码中。使用stdout支持版本同样非常简单。

注意POSIX规范为flockfile()通过Michael Burr在他answer引用片段:

引用(FILE *)对象,除了那些在_unlocked结尾的名称的所有功能,应表现为如果他们在内部使用flockfile()funlockfile()来获取这些(FILE *)对象的所有权。

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

+0

我还没有完全理解你的答案,但我已经得到了你对我说的话。简而言之,你说用多线程使用printf()打印的字符串可以在字符级混合,不是吗?感谢您花时间搜索大量信息来回答我的问题! –

+0

是的,我在说'printf()'打印的字符串可以在字符级别被不同的线程交织。试图把所有的东西放在一起很有趣。我不确定它是否像我想要的那样稳定,但我认为这个线索足够好 - 除非有人想出另一个推导和一个不同的结论。 –