【问题标题】:printf calls messed up with std::thread but std::cout are fineprintf 调用搞砸了 std::thread 但 std::cout 很好
【发布时间】:2016-12-29 17:50:22
【问题描述】:

当使用 printf (stdio.h) 时,"Start both threads\n" 和 "Enter a number\n" 混合在一起(类似于 "StEanterrt ba oth thnureaber\nads\n"),但这不会发生在 std::cout (iostream) 中。我怀疑这与 std::thread 有关,但我还是多线程编程的新手。

我真的不想使用 iostream,因为它让程序变得超级大!

我正在使用 mingw32 g++ 4.9.2,使用 g++ -o foreback foreback.cpp -O2 -std=c++11 编译。 (虽然我的电脑是64位的,但是我发现mingw-w64产生的程序大约是mingw32的两倍,所以我没有使用它。

//#define IOS
#ifdef IOS
#include <iostream>
#endif
#include <stdio.h>
#include <thread>
#include <atomic>
#include <windows.h> // for Sleep()

std::atomic<int> atom(1);

void foreground() {
    int c = 1;
    while (c) {
#ifdef IOS
        std::cout << "Enter a number: ";
        std::cin >> c;
#else
        printf("Enter a number: ");
        scanf("%d", &c);
#endif
        atom.store(c, std::memory_order_relaxed);
    }
}

void background() {
    FILE *out = fopen("foreback.txt", "w");
    int c = 1;
    while (c) {
        fprintf(out, "%d", c);
        c = atom.load(std::memory_order_relaxed);
        Sleep(500);
    }
    fclose(out);
}

int main() {
    std::thread f(foreground), b(background);
#ifdef IOS
    std::cout << "Start both threads.\n";
#else
    printf("Start both threads.\n");
#endif
    f.join();
    b.join();
#ifdef IOS
    std::cout << "End of both threads.\n";
#else
    printf("End of both threads.\n");
#endif
    return 0;
}

【问题讨论】:

  • 为了记录,通常没有必要因为程序大小而弯曲变形。硬盘又大又快,程序被半延迟地映射到内存中,这样未使用的部分就不会延迟启动等。如果它运行得足够快,在可执行文件的大小上增加一个 MB 通常并不重要(我怀疑iostream 增加了那么多,尽管在 Windows 上,我不能 100% 确定它可以卸载到共享运行时库的多少)。
  • 对于带有 stdio.h 的简单 hello world 程序,gnu gcc 产生 8KB,mingw32 产生 30KB,mingw-w64 产生 59KB。不过既然你说1MB的差别也无所谓,那我就不用担心了吧?
  • 是的。即使是当今最慢、碎片最严重的旋转磁盘驱动器,其读取速度也只有几 MB/秒(如果没有碎片,则为几十或几百 MB/秒)。较小的计算机通常有大约 64 GB 的空间,其中大多数有 4-20 倍的空间;即使假设操作系统和其他应用程序使用 32 GB,使用 32 GB 可用空间中的 1 MB 通常并不重要,除非您实际上是在不同位置编译和安装程序的数百或数千个副本。 :-)

标签: c++ multithreading c++11


【解决方案1】:

std::cout 也不保证交错;它在 C++03 中没有提到,C++11 的 FDIS 在 §27.4.1 [iostream.objects.overview] 中说了以下内容:

多线程并发访问同步(第 27.5.3.4 节)标准 iostream 对象的格式化和未格式化输入(第 27.7.2.1 节)和输出(第 27.7.3.1 节)函数或标准 C 流不应产生数据种族(§1.10)。 [ 注意:如果用户希望避免交错字符,则仍必须同步多个线程对这些对象和流的并发使用。 ——尾注]

最后的注释基本上意味着“std::cout也允许交错字符”。可能由于编译器/运行时库的特定实现通常没有这样做,或者由于尝试to synchronize itself with stdio.h(关闭sync_with_stdio 可能会导致它再次开始交错)。但这不是语言保证;你只是走运了。

如果您希望输出不交错,您需要从单个线程执行所有 I/O(让您的工作人员接受参数和计算值,并且主线程负责执行 I/O 以输出计算的值),或者显式锁定所有 I/O 函数,这些函数从针对同一流/FILE* 的不同线程中使用。您可以轻松使用stdio.h,您只需要在使用stdio.h 函数时锁定一个std::mutex(例如使用std::lock_guard);只要始终如一地完成,就可以保证没有交错。

【讨论】:

  • 仅供参考,信用到期,我从 this answer 借用了规范引用,这使得 100% 清楚的是,虽然同步的标准 iostream 对象保证不会有数据竞争(损坏的流或丢弃/加倍输出),根本无法保证数据排序或交错。
  • 我是通过竞技编程开始学习编程的,所以我才这么不愿意使用iostream。根据您将 std::mutex 包装在 stdio.h 函数周围的建议,它现在可以正常工作。我很惊讶该程序的大小并没有增加太多,可能是因为 atomic 已经包含互斥锁。通过这个随机示例,我学到的东西比我预期的要多。谢谢!
  • @JohnLondon:std::mutex 不是在std::atomic 中复制的,而是std::mutex 是围绕操作系统提供的锁定原语的一个相当薄的包装器,它动态链接到您的程序,不是静态链接的。大多数std::mutex 行为基本上归结为定义一个结构,然后调用外部函数,在您自己的可执行文件中托管最少的逻辑。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-16
  • 2021-07-11
  • 1970-01-01
  • 2016-12-11
  • 1970-01-01
相关资源
最近更新 更多