Question Details

No question body available.

Tags

c multithreading

Answers (2)

December 31, 2025 Score: 3 Rep: 27,269 Quality: Medium Completeness: 80%

Is there any way to block new shared flocks while there is an exclusive flock pending?

Yes. The writer thread can indicate to the reader threads that it wants exclusive access to the lock by setting an atomic variable (such as an atomicbool) to a certain value (e.g. true). If all reader threads check this variable before attempting to acquire the shared lock, and don't attempt to acquire the lock as long as this variable is set to that value, then the writer will effectively have priority over the reader threads.

As far as I can tell, the pthreads library does not offer an efficient way for the reader threads to wait for the writer thread to be finished and the atomic variable to be reset to its original value. Using a pthreads condition variable would be inefficient, as this would require every reader thread to acquire a mutex, thereby preventing parallel execution. However, Linux allows you to create an "event" file descriptor using the eventfd system call. A writer thread can signal this event when it is finished, and the reader threads can wait for this event to become signalled using poll or epoll. See the following Stack Overflow question for further information:

Linux - how to wait on event without using mutex

Since the information of the atomic variable is duplicated in the event, this makes the atomic variable redundant in theory. However, since checking an atomic variable is a much cheaper operation than a system call to check the state of the event, I recommend keeping the atomic variable, and only using the event for waiting.

It is also worth noting that your program contains a bug in the form of a race condition. The lines

int pthreadnum = (int )threadnumdata;
int threadnum = *pthreadnum;

will read from the loop counter of the loop in the function main. However, this will only work as intended if that loop is still executing the same iteration of the loop in which the thread was started. If that loop is already executing the next loop iteration, then the loop counter will no longer have the desired value. To fix this, you can pass the value of the Thread ID (which must be cast to a pointer value), instead of a pointer to the loop counter.

I have rewritten your program as stated above:

/*
 * if VERBOSE is defined, the shared threads will
 * list every flock & unflock.
 */

#define VERBOSE

#include #include #include #include #include #include #include #include #include #include

atomicbool gwriterwantsaccess = false; int gevent;

void setevent( void ) { uint64t eventval = 1;

if ( write( gevent, &eventval, sizeof eventval ) != sizeof eventval ) { fprintf(stderr, "Error setting event!\n"); exit( EXITFAILURE ); } }

void clearevent( void ) { uint64t eventval;

if ( read( gevent, &eventval, sizeof eventval ) != sizeof eventval ) { fprintf( stderr, "Error clearing event!\n" ); exit( EXITFAILURE ); } }

void exclusiveflock() { // tell the reader threads to back off gwriterwantsaccess = true; clearevent();

int fd = open( "flockfile", ORDWR | OCREAT | OTRUNC );

printf("Waiting on exclusive flock\n"); flock(fd, LOCKEX); printf("Lock successfull!\n");

flock(fd, LOCKUN); printf("Unlocked\n");

// tell the reader threads that they may again proceed as usual gwriterwantsaccess = false; setevent();

close(fd); }

void shared_thread_routine( void threadnumdata ) { int threadnum = (intptrt)threadnumdata;

printf("thread %d starting to juggle shared flocks\n", threadnum);

// these threads get shared flocks for a while then // release them. // Since they are shared, they get issued immediately // (unless an exclusive flock succeeds). // they overlap, so there is never a time when none // of the threads have a shared flock. for (int i = 0; i < 30; i++) { int fd = open("flockfile", ORDWR | OCREAT | OTRUNC);

// determine whether to give way to writer thread while ( gwriterwantsaccess ) { struct pollfd pfd = { gevent, POLLIN, 0 };

printf( "thread %d giving way to writer thread\n", threadnum );

// wait for writer thread to finish if ( poll( &pfd, 1, -1 ) < 1 || pfd.revents & POLLIN == 0 ) { fprintf( stderr, "Error waiting for writer thread!\n" ); exit( EXITFAILURE ); } }

// open a shared flock flock(fd, LOCKSH);

#ifdef VERBOSE printf("thread %d has a shared flock on fd %d\n", threadnum, fd); #endif

// read the file, do some work sleep(1);

// release the flock; hopefully give exclusivethread a chance: flock(fd, LOCKUN); close(fd);

#ifdef VERBOSE printf("thread %d has released it's flock on fd %d\n", threadnum, fd); #endif

// a short delay between runs usleep(4000 (threadnum) ); } printf("thread %d done\n", threadnum); }

int main( int argc, char argv ) { pthread_t threads[10];

// create event file descriptor g_event = eventfd( 1, EFD_NONBLOCK ); if (g_event < 0) { fprintf(stderr, "Error creating event!\n"); exit( EXIT_FAILURE); }

// create the reader threads for ( int threadnum = 0; threadnum < 10; threadnum++ ) { pthread_create( &threads[threadnum], NULL, shared_thread_routine, (void)(intptrt)threadnum); usleep(1000); }

// attempt to obtain exlusive access using the writer thread exclusiveflock();

// join all the threads for ( int threadnum = 0; threadnum < 10; threadnum++ ) { pthreadjoin( threads[threadnum], NULL ); }

// cleanup close(gevent); }

This program has the following behavior:

thread 0 starting to juggle shared flocks thread 0 has a shared flock on fd 4 thread 1 starting to juggle shared flocks thread 1 has a shared flock on fd 5 thread 2 starting to juggle shared flocks thread 2 has a shared flock on fd 6 thread 3 starting to juggle shared flocks thread 3 has a shared flock on fd 7 thread 4 starting to juggle shared flocks thread 4 has a shared flock on fd 8 thread 5 starting to juggle shared flocks thread 5 has a shared flock on fd 9 thread 6 starting to juggle shared flocks thread 6 has a shared flock on fd 10 thread 7 starting to juggle shared flocks thread 7 has a shared flock on fd 11 thread 8 starting to juggle shared flocks thread 8 has a shared flock on fd 12 thread 9 starting to juggle shared flocks thread 9 has a shared flock on fd 13 Waiting on exclusive flock thread 0 has released it's flock on fd 4 thread 0 giving way to writer thread thread 1 has released it's flock on fd 5 thread 2 has released it's flock on fd 6 thread 3 has released it's flock on fd 7 thread 4 has released it's flock on fd 8 thread 8 has released it's flock on fd 12 thread 2 giving way to writer thread thread 5 has released it's flock on fd 9 thread 6 has released it's flock on fd 10 thread 9 has released it's flock on fd 13 Lock successfull! Unlocked thread 7 has released it's flock on fd 11 thread 0 has a shared flock on fd 4 thread 2 has a shared flock on fd 5 thread 1 has a shared flock on fd 6 thread 3 has a shared flock on fd 7 thread 4 has a shared flock on fd 8 thread 5 has a shared flock on fd 9 thread 6 has a shared flock on fd 10 thread 8 has a shared flock on fd 11 thread 7 has a shared flock on fd 12 thread 9 has a shared flock on fd 13 thread 0 has released it's flock on fd 4 thread 2 has released it's flock on fd 5 thread 0 has a shared flock on fd 4 thread 1 has released it's flock on fd 6 thread 3 has released it's flock on fd 7 thread 1 has a shared flock on fd 5 thread 4 has released it's flock on fd 8 thread 2 has a shared flock on fd 6 thread 3 has a shared flock on fd 7 thread 5 has released it's flock on fd 9 [...]

The order of the events in the output above is not always accurate, because if two threads attempt to print at nearly the same time, then one thread may be faster and/or be scheduled earlier than the other thread for printing.

However, using flock file locking does not seem like the ideal solution in this case, because the function flock is an expensive system call. Using a pthreads read-write lock may be a better solution. Although POSIX does not support prioritizing writers when using a read-write lock, on Linux, it is supported as an extension. See the following thread for further information:

How to prevent writer starvation in a read write lock in pthreads

I have rewritten the program to use a pthreads read-write lock:

#include 
#include 
#include 
#include 
#include 

pthreadrwlockt grwlock;

void exclusivelock() { int fd = open( "flockfile", ORDWR | OCREAT | OTRUNC );

// obtain write lock printf("Waiting on write lock\n"); if ( pthreadrwlockwrlock( &grwlock ) != 0 ) { fprintf( stderr, "Error obtaining write lock!\n" ); exit( EXITFAILURE ); } printf("Lock successfull!\n");

// release write lock pthreadrwlockunlock( &grwlock ); printf("Unlocked\n");

// cleanup close(fd); }

void shared_thread_routine( void threadnumdata ) { int threadnum = (intptrt)threadnumdata;

printf("thread %d starting to juggle read locks\n", threadnum);

// these threads get shared flocks for a while then // release them. // Since they are shared, they get issued immediately // (unless an exclusive flock succeeds). // they overlap, so there is never a time when none // of the threads have a shared flock. for (int i = 0; i < 30; i++) { int fd = open("flockfile", ORDWR | OCREAT | OTRUNC);

// obtain read lock printf("thread %d waiting on read lock on fd %d\n", threadnum, fd); if ( pthreadrwlockrdlock( &grwlock ) != 0 ) { fprintf( stderr, "Error obtaining read lock!\n" ); exit( EXITFAILURE ); } printf("thread %d has a read lock on fd %d\n", threadnum, fd);

// read the file, do some work sleep(1);

// release the read lock pthreadrwlockunlock( &grwlock ); printf("thread %d has released its read lock on fd %d\n", threadnum, fd);

// cleanup close(fd);

// a short delay between runs usleep(4000 * (threadnum) ); } printf("thread %d done\n", threadnum); }

int main( int argc, char argv ) { pthreadt threads[10]; pthreadrwlockattrt rwlockattr;

// initialize read-write lock attributes if ( pthreadrwlockattrinit( &rwlockattr ) != 0 ) { fprintf( stderr, "Error initializing lock attributes!\n" ); exit( EXITFAILURE ); }

// set lock attributes to prioritize writers if ( pthreadrwlockattrsetkindnp( &rwlockattr, PTHREADRWLOCKPREFERWRITERNONRECURSIVENP ) != 0 ) { fprintf( stderr, "Error setting lock attributes!\n" ); exit( EXITFAILURE ); }

// initialize read-write lock if ( pthreadrwlockinit( &grwlock, &rwlockattr ) != 0 ) { fprintf( stderr, "Error initializing lock!\n" ); exit( EXITFAILURE ); }

// cleanup pthreadrwlockattrdestroy( &rwlockattr );

// create the reader threads for ( int threadnum = 0; threadnum < 10; threadnum++ ) { pthreadcreate( &threads[threadnum], NULL, sharedthreadroutine, (void*)(intptrt)threadnum); usleep(1000); }

// attempt to obtain exlusive access using the writer thread exclusivelock();

// join all the threads for ( int threadnum = 0; threadnum < 10; threadnum++ ) { pthreadjoin( threads[threadnum], NULL ); }

// cleanup pthreadrwlockdestroy( &g_rwlock ); }

This program has the following output:

thread 0 starting to juggle read locks thread 0 waiting on read lock on fd 3 thread 0 has a read lock on fd 3 thread 1 starting to juggle read locks thread 1 waiting on read lock on fd 4 thread 1 has a read lock on fd 4 thread 2 starting to juggle read locks thread 2 waiting on read lock on fd 5 thread 2 has a read lock on fd 5 thread 3 starting to juggle read locks thread 3 waiting on read lock on fd 6 thread 3 has a read lock on fd 6 thread 4 starting to juggle read locks thread 4 waiting on read lock on fd 7 thread 4 has a read lock on fd 7 thread 5 starting to juggle read locks thread 5 waiting on read lock on fd 8 thread 5 has a read lock on fd 8 thread 6 starting to juggle read locks thread 6 waiting on read lock on fd 9 thread 6 has a read lock on fd 9 thread 7 starting to juggle read locks thread 7 waiting on read lock on fd 10 thread 7 has a read lock on fd 10 thread 8 starting to juggle read locks thread 8 waiting on read lock on fd 11 thread 8 has a read lock on fd 11 thread 9 starting to juggle read locks thread 9 waiting on read lock on fd 12 thread 9 has a read lock on fd 12 Waiting on write lock thread 0 has released its read lock on fd 3 thread 0 waiting on read lock on fd 3 thread 1 has released its read lock on fd 4 thread 2 has released its read lock on fd 5 thread 3 has released its read lock on fd 6 thread 4 has released its read lock on fd 7 thread 1 waiting on read lock on fd 4 thread 5 has released its read lock on fd 8 thread 6 has released its read lock on fd 9 thread 7 has released its read lock on fd 10 thread 8 has released its read lock on fd 11 thread 9 has released its read lock on fd 12 Lock successfull! Unlocked thread 0 has a read lock on fd 3 thread 1 has a read lock on fd 4 thread 2 waiting on read lock on fd 5 thread 2 has a read lock on fd 5 thread 3 waiting on read lock on fd 6 thread 3 has a read lock on fd 6 thread 4 waiting on read lock on fd 7 thread 4 has a read lock on fd 7 thread 5 waiting on read lock on fd 8 thread 5 has a read lock on fd 8 thread 6 waiting on read lock on fd 9 thread 6 has a read lock on fd 9 thread 7 waiting on read lock on fd 10 thread 7 has a read lock on fd 10 thread 8 waiting on read lock on fd 11 thread 8 has a read lock on fd 11 thread 9 waiting on read lock on fd 12 thread 9 has a read lock on fd 12 thread 0 has released its read lock on fd 3 thread 1 has released its read lock on fd 4 thread 2 has released its read lock on fd 5 thread 0 waiting on read lock on fd 3 thread 0 has a read lock on fd 3 thread 1 waiting on read lock on fd 4
December 31, 2025 Score: 1 Rep: 35,145 Quality: Medium Completeness: 80%

You don't need to use any locking.

Use rename().

Assuming this is a POSIX system and you're running on a local filesystem that supports atomic rename() operations (and if you're trying to do file locking on any non-local, network file system, give up now), and every file access by a reader is done via open()/read()/close() operations, don't bother using locks.

Simply write a new file in the same filesystem as the working copy, then call rename( "/path/to/new/file", /path/to/working/copy" ). Any open file descriptor on the original working copy of the file will remain valid, allowing any reader to have an unmodified copy of the data, and any open() call made after the rename() will use the new copy. And once the last open file descriptor on the old file is closed, the old file will be deleted.

And you wondered why Unix allows things like deleting or renaming a file while it's open? Now you know.