缺少C11线程支持时,C标准库函数是否是线程安全的?

4

我正在Windows上编写一个多线程程序。由于我不知道有没有支持C11线程的Windows实现,因此最好的选择是使用本地WinAPI多线程。但是有一个问题。 C库中的某些函数(例如malloc或I/O函数)被C11标准要求具备线程安全性。但是,即使定义了__STDC_NO_THREADS__,它们是否仍然需要具备线程安全性呢?如果C实现中不存在线程设施,它们似乎没有必要具备线程安全性,但这真的有所帮助,因为我真的不想在所有I/O函数中都包装互斥锁。


2
我似乎无法解决“我正在编写一个多线程程序”和“实现中不存在线程功能”的矛盾。 - goodvibration
@goodvibration 我的意思是C语言实现。正如我一开始所说,我正在使用本地Win32线程。 - DarkAtom
1
线程安全是在C11之前的一个概念。 - Christian Gibbons
@ChristianGibbons 显然是这样,但是自从C11引入线程以来,一些C函数被要求具备线程安全性。我的实现不支持它们(在标准中是可选的),我想知道这些函数(malloc()printf()等)是否仍然具有线程安全性。 - DarkAtom
1
你将需要在MSDN上查阅相关文档,但通常情况下是这样的,至少如果你正在链接多线程运行时。 - user1143634
显示剩余2条评论
4个回答

4

谢谢!我不知道我必须使用_beginthread()而不是CreateThread() - DarkAtom
1
在C或C++中使用线程,你需要使用beginthread。这是一个非常古老的迷思。只要不从你的线程中调用CRT,你就可以很好地使用CreateThread。我认为这个迷思的起源是Visual Studio和/或Microsoft CRT中的一个bug,而不是实际的Win API。如果我没记错的话,它与MFC有关。 - Lundin
1
@lun,你记错了。这与MFC无关。而你提到的“非常古老的神话”是指微软CRT实现中的一个bug,如果使用CRT的线程没有使用_beginthread创建,则会导致内存泄漏。该bug已经修复。然而,记录的问题仍然存在。 - IInspectable
1
@Lundin 如果文档中建议使用 _beginthread(),那么你应该使用它。如果 GCC 文档中建议使用 -pthread 而不是 -lpthread,那么你也应该这样做。如果有人费心记录这些内容,就请相信他们有充分的理由。 - Zan Lynx
@IInspectable 这就是我写的内容:“只要不从你的线程调用CRT,你就可以很好地使用CreateThread。” - Lundin
1
@lun 为什么要制造那么多噪音呢?很明显,OP正在使用CRT,就像几乎所有非平凡的应用程序一样。现在你真正要说的话是更加危险的:你称文档是错误的,因为你从未观察到任何问题,因为你绕过了它们的前提条件。这是疏忽的,甚至是犯罪的。 - IInspectable

0
不,标准库函数不能保证线程安全,无论使用哪种线程库。C11 7.1.4/4明确说明:
“标准库中的函数不能保证可重入,并且可能修改具有静态或线程存储期的对象。”
特定的标准库实现或库标准扩展可能会根据情况提供线程安全函数。

你读过7.1.4/5吗?例如,malloc()不会修改静态或线程存储,因此它是线程安全的。 - DarkAtom
@DarkAtom 标准库中可以进行特定的异常处理。对于malloc函数而言,相关部分是7.22.3/2:“为了确定数据竞争的存在,内存分配函数的行为就像它们只访问通过参数可访问的内存位置,而不是其他静态持续存储。这些函数可能会明显地修改它们分配或释放的存储。”但通常情况下,标准库函数并不会进行此类异常处理。 - Lundin
你显然没有完全阅读问题。我特别提到了malloc()和相关函数(根据7.22.3保证是线程安全的)以及I/O函数(根据7.21.2/7和7.21.2/8保证是线程安全的)。显然,我并没有指的是像strtok()这样具有静态存储的函数,它们无法保证线程安全。 - DarkAtom
@DarkAtom,我完全理解了这个问题,让我为您引用一下:“在没有C11线程的情况下,C标准库函数是否是线程安全的?” - Lundin
C语言库中的一些函数,如malloc或I/O函数,需要是线程安全的。但是,即使定义了__STDC_NO_THREADS__,它们也需要是线程安全的吗? - DarkAtom

0

即使在C11中,标准内部的原子性或线程安全性保证仅与由标准定义的信号或线程相关。如果通过标准未指定的某些方式创建线程,则该线程可能执行的任何操作(包括其与其他线程的可能交互)都将超出标准的管辖范围。

针对可以创建线程或触发异步信号的系统进行低级编程的高质量实现通常允许程序以超出标准要求的方式与这些内容进行交互,因为标准基本上没有提供任何内容。


-1

最明显且可避免的是那些使用并经常返回子例程静态数据元素的函数,尽管名称上下文是本地的,但它们当然在堆上。它们很少见,但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 man页面呢? - DarkAtom
C语言和man手册并不关心操作系统,因为大多数都是非常可移植的。函数要么是线程安全的,要么不是。它们可能在一些参考文献中被标记为这样。如果您编写它们,则会出现“必需”的单词。使它们线程安全的一种方法是避免使用全局变量和静态变量,或使用互斥锁来保护共享资源。另一条通往线程安全的道路是单调递增的,因为并发线程可能会看到旧值(陈旧的缓存),从而无法启用问题。我编写了一个具有无符号长整型输入和输出计数器的环形缓冲区。 - David G. Pickett

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接