9

I'm writing a C solution to some variable of the dining savages problem. Now, I create threads which every thread gets a FILE* to the same debug file; inside the thread I'm doing some printing using fprintf(). The printed statements are not protected by any kind of mutex etc..

I don't observe any interleaving behavior in the debug file; it seems that it is thread safe although nowhere online I found a definitive statement that this is the case.

What I did see is the following:

  1. in Unix the fprintf is thread safe
  2. using the c++11 compiler the fprintf needs to be thread safe

The reason I'm asking that is because this is a university assignment that works, but I still have the doubt that in another windows based computer the program can cause a problem due to the uncertainty discussed above.

I’ll attach the thread code so you will see that the prinintg is not protected:

    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;
}

just reinforming, this runs on windows using visual 2015

will appreciate some help!!!

**in case you will need some more code i will add, although the rest are not that informative to the question asked

Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
David
  • 8,113
  • 2
  • 17
  • 36
  • which version of the CRT are you linking to? – MFisherKDX Dec 21 '17 at 18:33
  • @MFisherKDX i dont really know how to check it, any suggestions? or methods to checks it – David Dec 21 '17 at 18:34
  • It's been several years since I've used Visual Studio. But I believe you can find which CRT you are linking to from the Project Properties / Code Generation tab. If you are linking to the multi-threaded version of the CRT, I believe the answer is, "Yes" `fprintf` is thread safe. In fact, here's a reference: https://stackoverflow.com/questions/22539282/is-msvcrts-implementation-of-fprintf-thread-safe – MFisherKDX Dec 21 '17 at 18:39
  • 1
    @MFisherKDX ok i found the tab, i see that in the runtime library it is Multi-threaded (/MT) is that what you intended to? if so can i reinsure before the program start running that ill use the multi thread version? – David Dec 21 '17 at 18:42
  • @chemist: I'm pretty sure they removed the old single-threaded library, since you can't even buy single-core CPU's anymore. – MSalters Dec 21 '17 at 18:49
  • 1
    yeah, that the one chemist. And @MSalters is right ... the single threaded library has been removed since VS 2005. – MFisherKDX Dec 21 '17 at 18:57
  • @MFisherKDX so i can assume that as long as i run on a newer version of visual the fprintf will be thread safe? can i check at the beginning of the start the type of visual studio the program run on, and to quit in particular versions? – David Dec 21 '17 at 20:54
  • *so i can assume that as long as i run on a newer version of visual the fprintf will be thread safe?* Yes. (See answer below). *can i check at the beginning of the start the type of visual studio the program run on, and to quit in particular versions*. The deployed program won't be run inside a visual studio environment. But this check isn't necessary. Your program will a) either link to the correct library on the user's machine if using dynamic linking, or b) already have statically linked to the correct code (static linking) https://msdn.microsoft.com/en-us/library/2kzt1wy3(v=vs.140).aspx – MFisherKDX Dec 21 '17 at 21:14
  • Since this is C++, strongly suggest overloading `cout` rather than using `fprintf()` – user3629249 Dec 23 '17 at 14:04
  • you could eliminate the worry about `fprintf()` being thread safe by using a `ptrhead_mutex`, call `pthread_lock()` before calling fprintf()` and calling `pthread_unlock()` afterwards. – user3629249 Dec 23 '17 at 14:07
  • 1
    @user3629249 its not C++ its C i'm using the CreateThread() function, i don't think that there is an equivalent in C to what you are saying in your comment – David Dec 23 '17 at 18:31

2 Answers2

9

C2011 -- the first version of the standard to acknowledge the existence of threads in the first place -- places no limitation on how fprintf() calls in different threads may or do interact. In that sense, fprintf() is not thread-safe.

POSIX, however, does specify that fprintf() calls from different threads of the same process do not interfere with each other, and if that if they both specify the same target file, their output will not be intermingled. POSIX-conforming fprintf() is thus thread-safe in that sense.

I cannot speak to whether standard C++ places requirements that have the effect of requiring fprintf() to be thread safe. I would find that surprising, but it could be true. To be sure, it is safe to write to an iostream object from multiple threads, but that does not imply that the same is true of fprintf().

But none of that really matters if you're asking about Windows C or C++, however, which (the C in particular) are well known to be non-conforming. If you want to know about Windows's fprintf() in particular, then that has already been answered here (yes).

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
  • 1
    i already saw that answer, im asking if that is trustworthy one, meaning it is correct and i can rely on it. not only this i want to make sure that i understand that correctly, meaning that since the compiler changed fprintf is indeed thread safe but for older compiler version it is not? if this is the case can i check in my main() before the start of the program what is the type of compiler the EXE is running on and in case it is older then something stop the program? – David Dec 21 '17 at 20:49
  • The `fprintf()` of Microsoft's multithreaded runtime library is thread safe. This is the only C runtime library that MS has shipped since 2005. For some time prior to that, it shipped both a multithreaded and a single-threaded flavor. The single-threaded flavor does not support multi-threaded programs. – John Bollinger Dec 21 '17 at 20:58
  • 1
    can you add a link to a place that said it formally? – David Dec 22 '17 at 17:18
0

Seems to me like the output could be intermingled:

From the GNU documentation:

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.

And the "MT-Safe" definition (emphasis mine):

MT-Safe or Thread-Safe functions are safe to call in the presence of other threads. MT, in MT-Safe, stands for Multi Thread.

Being MT-Safe does not imply a function is atomic, nor that it uses any of the memory synchronization mechanisms POSIX exposes to users. It is even possible that calling MT-Safe functions in sequence does not yield an MT-Safe combination. For example, having a thread call two MT-Safe functions one right after the other does not guarantee behavior equivalent to atomic execution of a combination of both functions, since concurrent calls in other threads may interfere in a destructive way.

Whole-program optimizations that could inline functions across library interfaces may expose unsafe reordering, and so performing inlining across the GNU C Library interface is not recommended. The documented MT-Safety status is not guaranteed under whole-program optimization. However, functions defined in user-visible headers are designed to be safe for inlining.