resultsway
resultsway

Reputation: 13300

Is this file operation thread safe?

I was wondering the thread safety of my PRINT macro and wrote a program to see if file operations in my situation is thread safe, so I added sleep in fn0 and found to be thread safe.

$cat t
fn1
fn0

prints both and doesnt overwrite.

Is the test good enough or are there other situations PS : Iam not sharing file pointers Iam happy with the ordering though (unordered / non sequenced is fine ) Iam only interested in corruption/overwriting - it seems like the file pointer is moved well.

-----------macro-------------

#define PRINT(args ...) if (logflag) { \
FILE *flog = fopen(LOGFILE, "a"); \
fprintf( flog, args); \
fclose(flog); \
}  fprintf(stderr, args); fflush(stderr);

-------------------test prog----------------------------

#include <stdio.h>
#include <pthread.h>

#define LOGFILE "t"
char c='1';
void *fn0(void* v)
{

    FILE *flog = fopen(LOGFILE, "a");
    //sleep(2);
    fprintf( flog,"%s\n", "fn0");
    fclose(flog);

    printf ("Enter value ");
    c=getchar();
}

void* fn1(void*v)
{
    FILE *flog = fopen(LOGFILE, "a");
    fprintf( flog,"%s\n", "fn1");
    fclose(flog);
}
int main()
{
    pthread_t t0;
    pthread_t t1;


    pthread_create(&t0, NULL, fn0, (void *) NULL);
    pthread_create(&t1, NULL, fn1, (void *) NULL);
    pthread_join(t1, NULL);
    pthread_join(t0, NULL);

    return 0;
}

Upvotes: 1

Views: 2304

Answers (2)

ash
ash

Reputation: 5155

That code is pretty close to being thread-safe. It is certainly thread-safe by the fact that it will not:

  • corrupt the process memory
  • lead to segfaults due to simultaneuos access of in-memory data structures.
  • lead to reads of partially written data (such as filling a buffer in one thread and reading in another thread at the same time)

However, as mentioned by David Heffeman, it is not thread-safe at the level of writing to file. It is close though. Using system calls to write instead of buffered IO, such as using open + write instead of fopen + fwrite, can be used to get atomic writes and thread-safety.

See this answer for more details on making sure writes are atomic: Is file append atomic in UNIX?

One other thing; the use of global char c is risky, although it's use is thread-safe in the context shown. If c were declared inside the function fn0, it would eliminate potential issues (as long as it were kept an automatic variable and were not declared static).

On the topic of testing cases like these -- in my experience, stress testing can give reliable results, but it takes some effort to stress test - and stress tests usually bring out all kinds of nuances, which are great to fix, but can be overwhelming and frutstrating. In order to stress test, just setup a program that spins off a configurable number of threads over a configurable number of iterations. I recommend starting with 20 threads and 1000 iterations for a test like this.

With that said, I agree that thread-safety and race conditions can never be 100% proven not to exist through testing alone.

Upvotes: 2

David Heffernan
David Heffernan

Reputation: 612834

Your code is not threadsafe. If one of the threads attempts to open the file whilst the other thread has it open, the second attempt to open may fail with a sharing violation. Even if the OS allows you to open the file twice, simultaneously, you now have two separate unsynchronized buffers, and two distinct file pointers. There's absolutely no guarantee that your writes will be consistent.

You should serialize access to the shared resource with a mutex.

Alternatively, use a producer/consumer approach and put all writing to the log on a single thread, the consumer. Then let multiple producer threads push logging tasks onto the consumer thread.

Upvotes: 3

Related Questions