【问题标题】:Is fprintf() thread safe?fprintf() 线程安全吗?
【发布时间】:2025-12-28 22:40:11
【问题描述】:

我正在为餐饮野蛮人问题的某些变量编写 C 解决方案。 现在,我创建线程,每个线程都将 FILE* 获取到同一个调试文件;在线程内部,我正在使用 fprintf() 进行一些打印。打印的语句不受任何类型的互斥锁等保护。

我没有在调试文件中观察到任何交错行为;似乎它是线程安全的,虽然我没有在网上找到一个明确的声明,就是这种情况。

我看到的是以下内容:

  1. 在 Unix 中,fprintf 是线程安全的
  2. 使用 c++11 编译器,fprintf 需要是线程安全的

我之所以问这个问题是因为这是一项有效的大学作业,但我仍然怀疑在另一台基于 Windows 的计算机中,由于上述不确定性,该程序可能会导致问题。

我将附上线程代码,以便您看到打印不受保护:

    DWORD WINAPI RoomateThread(LPVOID lpParam) {
    /*=================================================
    "RoomateThread" this is the roomate thread handler function where
    the thread logic is implemented
    Input:  1. lpParam holds a roomate, runtime and pointers to both files
    Output: 1. return an DWORD value {0}->Success {-1}->Failure
            2. A code telling if the run was successful (debug file)
    A roommate follows this logic after wake up:
        1. If there are clothes available in the closet:
            a. Wait for mutex to be available, take it
            b. Check if the basket is full
                b.1. If it is - start the machine and wait for it to finish
            c. Throw an item in the basket
            d. release mutex
        2. If the closet is empty, wait for laundry_is_empty signal, then goto (1.a)
    =================================================*/
    /*variable declerations*/
    DWORD wait_res, delta;
    BOOL release_res;
    LONG previous_count;
    roomate_thread  *elem;
    elem = (roomate_thread*)lpParam;
    /*thread logic*/
    while (TRUE) {
        /*calculate the delta between the total run time and the time the roomate had run so far*/
        delta = total_time - elem->run_time;
        /*wait until the minimum between period Ti and delta*/
        Sleep(min(elem->roomate->run_time,delta));
        fprintf(elem->debug, "RoomateThread(): Line %d, roomate %d: slept for %d mili sec, starting...\n", __LINE__, elem->roomate->roomate_id, min(elem->roomate->run_time, delta));
        /*as instructed, each roomate is active since the wakeup*/
        fprintf(elem->report, "\nRoomate %d active", elem->roomate->roomate_id);
        /*update the element total run time since start*/
        elem->run_time = elem->run_time + min(elem->roomate->run_time, delta);
        if (time_to_stop < elem->run_time) {
            /*if the element total run time is bigger then the global variable update the global*/
            time_to_stop = elem->run_time;
        }
        /*its time to close the thread properly*/
        if (time_to_stop == total_time) {
            /*if the laundry basket has clothes in it, and the roomate run as much as the total time
            activate rhe robot once more and exit*/
            if (elem->run_time == total_time && items_in_laundry!=0) {
                release_res = ReleaseSemaphore(
                    laundry_is_full,
                    1,
                    &previous_count);
                if (release_res == FALSE) {
                    fprintf(elem->debug, "MachineThread(): Line %d, released semaphore 'laundry_is_full' failed\nthe last error is: 0X%x\n", __LINE__, GetLastError());
                    return FAILURE;
                }
            }
            break;
        }
        /*checks that the roomate has clothes in the closet to continue*/
        if (elem->roomate->clothes_in_laundry < elem->roomate->clothes-1) {/*roomate has clothes available*/
            fprintf(elem->debug, "RoomateThread(): Line %d,  roomate id= %d, number of dirty clothes=%d, number of total dirty clothes=%d\n",__LINE__, elem->roomate->roomate_id, elem->roomate->clothes_in_laundry, items_in_laundry);
        }
        // It's empty:
        else {
            /*waits until one of the roomates will activate the robot, cause there is no clothes in the roomate closet*/
            fprintf(elem->debug, "DAVIDS roomate %d have no clothes, waiting!!!\n", elem->roomate->roomate_id);
            elem->roomate->closet_empty = TRUE;
            /*Wait until the machine is done*/
            wait_res = WaitForSingleObject(laundry_is_empty, INFINITE);
            fprintf(elem->debug, "RoomateThread(): Line %d, roomate %d have clothes,finish waiting!!!\n",__LINE__, elem->roomate->roomate_id);
            if (wait_res != WAIT_OBJECT_0) {
                fprintf(elem->debug, "RoomateThread() error: Line %d, waiting for sempahore 'laundry_is_empty' failed\nthe last error is: 0X%x\n", __LINE__,GetLastError());
                return FAILURE;
            }
            fprintf(elem->debug, "RoomateThread(): Line %d, laundry_is_empty semaphore aquired , roomate: %d\n", __LINE__,elem->roomate->roomate_id);
        }
            /* Wait for mutex (machine start and clothes add "rights")*/
            wait_res = WaitForSingleObject(mutex, INFINITE);
            if (wait_res != WAIT_OBJECT_0) {
                fprintf(elem->debug, "RoomateThread() error: Line %d, waiting for 'mutex' failed\nthe last error is: 0X%x\n", __LINE__,GetLastError());
                return FAILURE;
            }
            fprintf(elem->debug, "RoomateThread(): Line %d, mutex aquired , roomate: %d\n", __LINE__, elem->roomate->roomate_id);
            fprintf(elem->debug, "RoomateThread(): Line 200, mutex aquired , roomate: %d\n",elem->roomate->roomate_id);
            /*Check if basket it full*/
            if (items_in_laundry == total_items) {
            /*Start Machine*/
            release_res = ReleaseSemaphore(
                    laundry_is_full,
                    1,
                    &previous_count);
            if (release_res == FALSE) {
                fprintf(elem->debug, "MachineThread(): Line %d, released semaphore 'laundry_is_empty' failed\nthe last error is: 0X%x\n", __LINE__, GetLastError());
                return FAILURE;
            }
            fprintf(elem->debug, "RoomateThread(): Line 210, released semaphore 'laundry_is_full' last  count is: %ld\n", previous_count);
            /*Wait for it to finish*/
            wait_res = WaitForSingleObject(laundry_is_empty, INFINITE);
            if (wait_res != WAIT_OBJECT_0) {
                fprintf(elem->debug, "RoomateThread() error: Line %d, waiting for sempahore 'laundry_is_empty' failed\nthe last error is: 0X%x\n", __LINE__, GetLastError());
                return FAILURE;
            }
            items_in_laundry = 0;
        }
        /*Throw in a dirty cloth*/
        elem->roomate->clothes_in_laundry++;
        items_in_laundry++;
        /*Release the mutex*/
        release_res = ReleaseMutex(mutex);
        if (release_res == FALSE) {
            fprintf(elem->debug, "RoomateThread(): Line %d, released 'mutex' failed\nthe last error is: 0X%x\n", __LINE__, GetLastError());
            return FAILURE;
        }
        fprintf(elem->debug, "RoomateThread(): Line %d, mutex released , roomate: %d\n", __LINE__, elem->roomate->roomate_id);
    }
    fprintf(elem->debug, "RoomateThread(): Line %d, thread of roomate %d ended\n", __LINE__, elem->roomate->roomate_id);
    return SUCSSES;
}

只是提醒一下,这在使用 Visual 2015 的 Windows 上运行

将不胜感激!

**如果您需要更多代码,我将添加,尽管其余代码对所提出的问题没有那么丰富的信息

【问题讨论】:

  • 您要链接到哪个版本的 CRT?
  • @MFisherKDX 我真的不知道如何检查它,有什么建议吗?或检查方法
  • @MFisherKDX 好的,我找到了该选项卡,我看到在运行时库中它是多线程 (/MT) 是您想要的吗?如果是这样,我可以在程序开始运行之前重新确保不使用多线程版本吗?
  • 是的,那一位化学家。 @MSalters 是对的......自 VS 2005 以来,单线程库已被删除。
  • @user3629249 它不是 C++ 它的 C 我正在使用 CreateThread() 函数,我认为 C 中没有你在评论中所说的等价物

标签: c windows multithreading


【解决方案1】:

C2011——第一个首先承认线程存在的标准版本——没有限制不同线程中的fprintf()调用如何交互。从这个意义上说,fprintf() 不是线程安全的。

但是,POSIX 确实指定来自同一进程的不同线程的fprintf() 调用不会相互干扰,如果它们都指定相同的目标文件,则它们的输出将不会混合。因此,符合 POSIX 的 fprintf() 在这个意义上是线程安全的。

我不能说标准 C++ 是否提出了要求fprintf() 是线程安全的要求。我会觉得这很令人惊讶,但它可能是真的。可以肯定的是,从多个线程写入iostream 对象是安全的,但这并不意味着fprintf() 也是如此。

但是,如果您询问的是 Windows C 或 C++,那么这些都不重要,但是众所周知,Windows C 或 C++(尤其是 C)是不符合标准的。如果你想特别了解 Windows 的fprintf(),那么that has already been answered here(是的)。

【讨论】:

  • 我已经看到了那个答案,我问这个答案是否值得信赖,这意味着它是正确的,我可以依赖它。不仅如此,我还想确保我理解正确,这意味着由于编译器更改了 fprintf 确实是线程安全的,但对于较旧的编译器版本却不是?如果是这种情况,我可以在程序启动之前检查我的 main() 运行 EXE 的编译器的类型是什么,如果它更旧,那么程序会停止吗?
  • 微软多线程运行时库的fprintf() 是线程安全的。这是 MS 自 2005 年以来唯一发布的 C 运行时库。在此之前的一段时间,它同时发布了多线程和单线程风格。单线程风格不支持多线程程序。
  • 你能添加一个链接到一个正式的地方吗?
【解决方案2】:

在我看来,输出可能是混合的:

来自GNU documentation

函数:int fprintf (FILE *stream, const char *template, ...)

Preliminary: | MT-Safe locale | AS-Unsafe corrupt heap | AC-Unsafe mem lock corrupt | See POSIX Safety Concepts.

This function is just like printf, except that the output is written to the stream stream instead of stdout.

以及“MT-Safe”定义(强调我的):

MT-Safe 或 Thread-Safe 函数在存在的情况下可以安全调用 其他线程。 MT,在 MT-Safe 中,代表多线程。

MT-Safe 并不意味着函数是原子的,也不意味着它使用 POSIX 向用户公开的任何内存同步机制。 甚至可以按顺序调用 MT-Safe 函数 不会产生 MT-Safe 组合。例如,有一个线程调用 两个 MT-Safe 功能一个接一个并不能保证 行为等同于两者结合的原子执行 函数,因为其他线程中的并发调用可能会干扰 破坏性的方式。

可以跨库内联函数的整个程序优化 接口可能会暴露不安全的重新排序,因此执行内联 不建议跨 GNU C 库接口。记录在案的 全程序优化下不保证 MT-Safety 状态。 然而,在用户可见的标题中定义的函数被设计为 内联安全。

【讨论】: