5

我使用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未完全打印。\n在第 3 行,末尾的换行符"Thread Thread Thread Thread\n"消失了。

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

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

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

真的是printf线程安全的吗?

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

另外,我一直不明白为什么输出上的一些单词消失了。

4

5 回答 5

4

POSIX 标准具有putc_unlocked()评论中所说的功能:

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

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

fprintf()由和生成的字符printf()被打印出来,就像fputc()被调用一样。

对于putc(),它说:

putc()函数应等效于fputc(),但如果将其实现为宏,则它可能会多次评估流,因此参数永远不应是具有副作用的表达式。

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

另一部分描述了线程并说:

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

下面的列表包括这些*_unlocked()功能。

因此,printf()并且fputc()必须是线程安全的,但是 by 的写入printf()是“好像” by 完成的fputc(),因此线程之间的输出交错可能是字符级别的,这或多或少与您所看到的一致。如果要对printf()非交错进行调用,则需要使用flockfile()andfunlockfile()调用来授予线程在执行stdout时的所有权。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()上面的函数调用来执行锁定、格式化、(刷新)和解锁操作。这可能是我编写代码的方式,相信编译器会在适当且可行的情况下内联代码。支持使用的版本stdout同样非常简单。

请注意Michael Burr在他的回答flockfile()中引用的 POSIX 规范片段:

所有引用 ( FILE *) 对象的函数,除了名称以 结尾的函数外_unlocked,都应该表现得好像它们使用flockfile()andfunlockfile()在内部获得这些 ( FILE *) 对象的所有权。

除了FILE *. 因此,我的fprintf_locked()功能应该是不必要的。fprintf()如果您发现不锁定文件的异常实现,则fprintf_locked()可以使用该函数,但只能在抗议的情况下完成 - 无论如何库都应该为您执行此操作。

于 2012-11-02T07:15:14.927 回答
3

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

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

所有引用 ( FILE *) 对象的函数的行为就像它们在内部使用flockfile() 和funlockfile() 来获得这些( FILE *) 对象的所有权一样。

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

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

于 2012-11-02T07:01:07.817 回答
0

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

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

于 2012-11-02T06:21:22.897 回答
0

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

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

于 2012-11-02T06:31:24.967 回答
0

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

于 2012-11-02T06:49:18.220 回答