【问题标题】:Are C standard library functions thread-safe in absence of C11 threads?在没有 C11 线程的情况下,C 标准库函数是否是线程安全的?
【发布时间】:2020-09-23 17:46:54
【问题描述】:

我正在 Windows 上编写一个多线程程序。由于我知道没有支持 C11 线程的 C 的 Windows 实现,因此我最好的选择是使用本机 WinAPI 多线程。但是有一个问题! C 库中的某些函数,例如 malloc 或 I/O 函数,要求 C11 标准是线程安全的。但是即使定义了__STDC_NO_THREADS__,它们是否也必须是线程安全的?如果 C 实现中不存在线程设施,那么它们是线程安全的似乎毫无意义,但它确实会有所帮助,因为我真的不想将所有 I/O 函数包装在互斥体中。

【问题讨论】:

  • 我似乎无法调和I am writing a multi-threaded programno thread facilities exist in the implementation 之间的矛盾。
  • @goodvibration 我的意思是说 C 实现。正如我一开始所说,我使用的是原生 Win32 线程。
  • 线程安全是 C11 之前的一个概念。
  • @ChristianGibbons 显然是这样,但是从 C11 开始,一些 C 函数被要求是线程安全的,因为它引入了线程。我的实现不支持它们(它们在标准中是可选的),我在问函数(malloc()printf() 等是否仍然是线程安全的)
  • 您必须查看 MSDN 上的相关文档,但大多数情况下是的,至少如果您要链接多线程运行时。

标签: c multithreading winapi c11


【解决方案1】:

如果您针对 Microsoft 多线程运行时库进行编译和链接,则诸如 malloc() 之类的函数将是线程安全的。

这不是语言保证,但由平台保证。

哦,还有read MSDN carefully。在 C 或 C++ 中使用线程,您需要使用 _beginthread()_beginthreadex(),而不是 Win32 CreateThread

还有更多:https://docs.microsoft.com/en-us/cpp/parallel/multithreading-with-c-and-win32?view=vs-2019

【讨论】:

  • 谢谢!我不知道我必须使用_beginthread() 而不是CreateThread()
  • “在 C 或 C++ 中使用线程你需要使用 beginthread” 这是一个非常古老的神话。只需不要从您的线程中调用 CRT,您就可以使用 CreateThread 就好了。我认为这个神话的起源是 Visual Studio 和/或 Microsoft CRT 中的一个错误,而不是实际的 Win API。如果我没记错的话,它与 MFC 有关。
  • @lun 你记错了。与 MFC 无关。而您所指的“非常古老的神话” 是Microsoft CRT 实现中的一个错误,如果使用CRT 的线程不是使用_beginthread 创建的,则会导致它泄漏内存。该错误已修复。但是,documented issue 仍然存在。
  • @Lundin 如果文档说要使用_beginthread(),那么您应该使用它。如果 GCC 文档说使用 -pthread 而不是 -lpthread 那么你也应该这样做。如果有人费心记录它,假设他们有充分的理由。
  • @IInspectable 这就是我写的:“只要不要从你的线程中调用 CRT,你就可以使用 CreateThread 就好了。”
【解决方案2】:

不,标准库函数不能保证是线程安全的,无论使用什么线程库。 C11 7.1.4/4 明确指出:

标准库中的函数不保证是可重入的,可能会修改具有静态或线程存储持续时间的对象。

特定的标准库实现或库标准扩展可能会根据具体情况提供线程安全函数。

【讨论】:

  • 你读过 7.1.4/5 吗?例如malloc(),不会修改静态或线程存储,因此是线程安全的。
  • @DarkAtom 可以在标准库中设置特定的例外。 malloc 的相关部分具体是 7.22.3/2 “为了确定是否存在数据竞争,内存分配函数的行为就像它们仅访问可通过其参数访问的内存位置而不是其他静态持续时间存储一样。这些函数可能,但是,明显地修改他们分配或取消分配的存储。”但一般来说,标准库函数不会出现此类例外。
  • 您显然没有完全阅读这个问题。我特别提到了malloc() 和朋友(7.22.3 保证线程安全)和 I/O 函数(7.21.2/7 和 7.21.2/8 保证线程安全)。显然我指的不是strtok() 之类的函数(它不能是线程安全的,因为它们有静态存储)。
  • @DarkAtom 我很好地阅读了这个问题,让我为您引用它:“C 标准库函数在没有 C11 线程的情况下是线程安全的吗?”
  • "C 库中的某些函数,例如 malloc 或 I/O 函数,要求是线程安全的", "但是即使定义了__STDC_NO_THREADS__,它们是否也要求是线程安全的?”。我不能把所有细节都放在问题的标题中,你必须阅读整个主题。
【解决方案3】:

即使在 C11 中,标准中的原子性或线程安全保证也仅对通过标准定义的方式创建的信号或线程有意义。如果一个线程是通过标准未指定的某种方式创建的,那么该线程可能做的任何事情,包括它与其他线程的可能交互,都将超出标准的管辖范围。

用于在可以创建线程或触发异步信号的系统上进行低级编程的高质量实现通常允许程序以超出标准要求的方式与此类事物进行交互,因为标准基本上不会提供任何内容。

【讨论】:

    【解决方案4】:

    最明显和可避免的是那些使用并经常返回子例程静态数据元素的函数,即使名称上下文是本地的,它当然在堆上。它们很少见,但我想到了 asctime() 和它的一些朋友。 https://linux.die.net/man/3/asctime 手册页说已过时,请使用 strftime()。然而,任何表现出“状态”的函数都可能有一个静态内部!例如,strtok(): https://linux.die.net/man/3/strtok 我从不喜欢它,我更喜欢手动解析我的字符串,而不是修改输入,使用指针派生的长度。所以要小心。一个优秀的 C 程序员可以编写大部分这些库例程,因此可以想象子例程静态(或文件静态全局或全局)用于提供“状态”的位置。

    顺便说一句,按照严格的定义,函数应该没有状态,但子程序可以!在 C++、JAVA 等中,方法将它们的状态保存在它们的对象或类中,对于线程支持,可以在一个或两个级别包含互斥锁,或者您可以使 JAVA 方法“同步”!

    【讨论】:

    • 这如何回答我的问题?标准不要求asctime()strtok() 是线程安全的(当然也不是)。我在询问线程安全所需的功能。另外,当我使用 Windows 时,你为什么要链接 Linux 手册页?
    • C 和手册页不关心操作系统,因为大多数都非常便携。函数要么是线程安全的,要么不是。在某些参考文献中,它们可能会被标记为这样。如果你写它们,那么“必需”这个词就会发挥作用。使它们成为线程安全的一种方法是避免全局变量和静态变量,或者使用互斥体保护公共资源。线程安全的另一条路径是单调递增,因为并发线程可能会看到不会导致问题的旧值(陈旧缓存)。我写了一个带有无符号长进出计数器的环形缓冲区。
    猜你喜欢
    • 2013-11-27
    • 1970-01-01
    • 2023-02-13
    • 2013-02-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-07-12
    • 1970-01-01
    相关资源
    最近更新 更多