Answer to: How can I flock LOCK_EX if there is always a LOCK_SH?
Score: 3
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 atomic_bool) 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 *)threadnum_data;
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 <stdio.h>
#include <stdlib.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stdint.h>
#include <unistd.h>
#include <sys/file.h>
#include <sys/eventfd.h>
#include <pthread.h>
#include <poll.h>
atomic_bool g_writer_wants_access = false;
int g_event;
void set_event( void )
{
uint64_t event_val = 1;
if ( write( g_event, &event_val, sizeof event_val ) != sizeof event_val )
{
fprintf(stderr, "Error setting event!\n");
exit( EXIT_FAILURE );
}
}
void clear_event( void )
{
uint64_t event_val;
if ( read( g_event, &event_val, sizeof event_val ) != sizeof event_val )
{
fprintf( stderr, "Error clearing event!\n" );
exit( EXIT_FAILURE );
}
}
void exclusive_flock()
{
// tell the reader threads to back off
g_writer_wants_access = true;
clear_event();
int fd = open( "flockfile", O_RDWR | O_CREAT | O_TRUNC );
printf("Waiting on exclusive flock\n");
flock(fd, LOCK_EX);
printf("Lock successfull!\n");
flock(fd, LOCK_UN);
printf("Unlocked\n");
// tell the reader threads that they may again proceed as usual
g_writer_wants_access = false;
set_event();
close(fd);
}
void *shared_thread_routine( void *threadnum_data )
{
int threadnum = (intptr_t)threadnum_data;
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", O_RDWR | O_CREAT | O_TRUNC);
// determine whether to give way to writer thread
while ( g_writer_wants_access )
{
struct pollfd pfd = { g_event, 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( EXIT_FAILURE );
}
}
// open a shared flock
flock(fd, LOCK_SH);
#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 exclusive_thread a chance:
flock(fd, LOCK_UN);
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*)(intptr_t)threadnum);
usleep(1000);
}
// attempt to obtain exlusive access using the writer thread
exclusive_flock();
// join all the threads
for ( int threadnum = 0; threadnum < 10; threadnum++ )
{
pthread_join( threads[threadnum], NULL );
}
// cleanup
close(g_event);
}
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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/file.h>
#include <pthread.h>
pthread_rwlock_t g_rwlock;
void exclusive_lock()
{
int fd = open( "flockfile", O_RDWR | O_CREAT | O_TRUNC );
// obtain write lock
printf("Waiting on write lock\n");
if ( pthread_rwlock_wrlock( &g_rwlock ) != 0 )
{
fprintf( stderr, "Error obtaining write lock!\n" );
exit( EXIT_FAILURE );
}
printf("Lock successfull!\n");
// release write lock
pthread_rwlock_unlock( &g_rwlock );
printf("Unlocked\n");
// cleanup
close(fd);
}
void *shared_thread_routine( void *threadnum_data )
{
int threadnum = (intptr_t)threadnum_data;
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", O_RDWR | O_CREAT | O_TRUNC);
// obtain read lock
printf("thread %d waiting on read lock on fd %d\n", threadnum, fd);
if ( pthread_rwlock_rdlock( &g_rwlock ) != 0 )
{
fprintf( stderr, "Error obtaining read lock!\n" );
exit( EXIT_FAILURE );
}
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
pthread_rwlock_unlock( &g_rwlock );
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 )
{
pthread_t threads[10];
pthread_rwlockattr_t rwlockattr;
// initialize read-write lock attributes
if ( pthread_rwlockattr_init( &rwlockattr ) != 0 )
{
fprintf( stderr, "Error initializing lock attributes!\n" );
exit( EXIT_FAILURE );
}
// set lock attributes to prioritize writers
if ( pthread_rwlockattr_setkind_np( &rwlockattr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP ) != 0 )
{
fprintf( stderr, "Error setting lock attributes!\n" );
exit( EXIT_FAILURE );
}
// initialize read-write lock
if ( pthread_rwlock_init( &g_rwlock, &rwlockattr ) != 0 )
{
fprintf( stderr, "Error initializing lock!\n" );
exit( EXIT_FAILURE );
}
// cleanup
pthread_rwlockattr_destroy( &rwlockattr );
// create the reader threads
for ( int threadnum = 0; threadnum < 10; threadnum++ )
{
pthread_create( &threads[threadnum], NULL, shared_thread_routine, (void*)(intptr_t)threadnum);
usleep(1000);
}
// attempt to obtain exlusive access using the writer thread
exclusive_lock();
// join all the threads
for ( int threadnum = 0; threadnum < 10; threadnum++ )
{
pthread_join( threads[threadnum], NULL );
}
// cleanup
pthread_rwlock_destroy( &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
View Question ↗
Question
Parent Entity
Score: 10 • Views: 200
Site: stackoverflow
Other Comments / Reviews
SaaS Metrics