fprintf()函数是线程安全的吗?

10
我正在编写一个 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;
}

只是再次提醒,这需要在Windows上使用Visual 2015运行。

非常感谢您的帮助!!!

**如果您需要更多的代码,我可以添加,尽管其余部分对所问的问题没有太多信息。


你链接的是哪个版本的CRT? - MFisherKDX
1
@MFisherKDX 好的,我找到了选项卡,我看到在运行库中它是多线程(/MT)。这是你想要的吗?如果是的话,在程序开始运行之前,我可以再次确认我会使用多线程版本吗? - David
@chemist:我非常确定他们已经移除了旧的单线程库,因为现在甚至都买不到单核CPU了。 - MSalters
1
是的,那个化学家。@MSalters说得对...自VS 2005以来,单线程库已被移除。 - MFisherKDX
1
@user3629249,这不是C++而是C语言。 我正在使用CreateThread()函数,我认为在你的评论中所说的C等价物不存在。 - David
显示剩余6条评论
2个回答

9

C2011标准是第一个承认线程存在的版本,它没有限制不同线程中fprintf()调用如何相互作用或者是否相互作用。从这个意义上讲,fprintf()不是线程安全的。

然而,POSIX规定同一进程的不同线程中的fprintf()调用不会相互干扰,如果它们都指定了相同的目标文件,则它们的输出不会交织在一起。因此,符合POSIX标准的fprintf()在这个意义上是线程安全的。

我无法确定标准C++是否有要求fprintf()必须是线程安全的效果。我觉得这很令人惊讶,但可能是真的。确保从多个线程写入iostream对象是安全的,但这并不意味着fprintf()也是如此。

但是,如果你问的是关于Windows的C或C++,那么这些语言(尤其是C)众所周知不符合标准。如果你想了解Windows特定的fprintf()函数,那么这个问题已经在这里得到了回答(是的)。

1
我已经看到了那个答案,我想知道它是否可靠,也就是说它是正确的,我可以依赖它。不仅如此,我还想确保我理解得正确,也就是说,由于编译器更改,fprintf 确实是线程安全的,但对于旧版本的编译器来说则不是吗? 如果是这种情况,我能否在程序开始之前的 main() 中检查 EXE 运行的编译器类型,并在其为旧版本时停止程序? - David
微软的多线程运行时库中的 fprintf() 是线程安全的。自2005年以来,这是微软唯一提供的 C 运行时库。在此之前的一段时间内,它同时提供了多线程和单线程版本。单线程版本不支持多线程程序。 - John Bollinger
1
你能否添加一个正式说明的链接? - David

0

我觉得输出可能会交织在一起:

来自GNU文档

Function: 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中代表Multi Thread。

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

整个程序优化可能会跨库接口内联函数,因此不建议在GNU C库接口上执行内联操作。文档化的MT-Safety状态在整个程序优化下不能保证。但是,在用户可见头文件中定义的函数被设计为可以安全地进行内联。


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