diff --git a/Custom_read_write_lock/CMakeLists.txt b/Custom_read_write_lock/CMakeLists.txt new file mode 100644 index 0000000..aca8dbe --- /dev/null +++ b/Custom_read_write_lock/CMakeLists.txt @@ -0,0 +1,21 @@ +# CMakeList.txt : CMake project for Custom_read_write_lock, include source and define +# project specific logic here. +# +cmake_minimum_required (VERSION 3.8) + +# Enable Hot Reload for MSVC compilers if supported. +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) + set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "$,$>,$<$:EditAndContinue>,$<$:ProgramDatabase>>") +endif() + +project ("Custom_read_write_lock") + +# Add source to this project's executable. +add_executable (Custom_read_write_lock "Custom_read_write_lock.c" "Custom_read_write_lock.h") + +if (CMAKE_VERSION VERSION_GREATER 3.12) + set_property(TARGET Custom_read_write_lock PROPERTY CXX_STANDARD 20) +endif() + +# TODO: Add tests and install targets if needed. diff --git a/Custom_read_write_lock/CMakePresets.json b/Custom_read_write_lock/CMakePresets.json new file mode 100644 index 0000000..f4bc98b --- /dev/null +++ b/Custom_read_write_lock/CMakePresets.json @@ -0,0 +1,101 @@ +{ + "version": 3, + "configurePresets": [ + { + "name": "windows-base", + "hidden": true, + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_C_COMPILER": "cl.exe", + "CMAKE_CXX_COMPILER": "cl.exe" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Windows" + } + }, + { + "name": "x64-debug", + "displayName": "x64 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x64", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x64-release", + "displayName": "x64 Release", + "inherits": "x64-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "x86-debug", + "displayName": "x86 Debug", + "inherits": "windows-base", + "architecture": { + "value": "x86", + "strategy": "external" + }, + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } + }, + { + "name": "x86-release", + "displayName": "x86 Release", + "inherits": "x86-debug", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } + }, + { + "name": "linux-debug", + "displayName": "Linux Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Linux" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + }, + { + "name": "macos-debug", + "displayName": "macOS Debug", + "generator": "Ninja", + "binaryDir": "${sourceDir}/out/build/${presetName}", + "installDir": "${sourceDir}/out/install/${presetName}", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + }, + "condition": { + "type": "equals", + "lhs": "${hostSystemName}", + "rhs": "Darwin" + }, + "vendor": { + "microsoft.com/VisualStudioRemoteSettings/CMake/1.0": { + "sourceDir": "$env{HOME}/.vs/$ms{projectDirName}" + } + } + } + ] +} diff --git a/Custom_read_write_lock/Custom_read_write_lock.c b/Custom_read_write_lock/Custom_read_write_lock.c new file mode 100644 index 0000000..66bfb22 --- /dev/null +++ b/Custom_read_write_lock/Custom_read_write_lock.c @@ -0,0 +1,243 @@ +#include "Custom_read_write_lock.h" + +void +rw_lock_init(rwlock_t* rw_lock) { + + pthread_mutex_init(&rw_lock->stateMutex, NULL); + pthread_cond_init(&rw_lock->cv, NULL); + rw_lock->n_reader_waiting = 0; + rw_lock->n_writer_waiting = 0; + rw_lock->is_locked_by_reader = false; + rw_lock->is_locked_by_writer = false; + rw_lock->writer_thread = 0; + +} + +void +rw_lock_rd_lock(rwlock_t* rw_lock) { + + pthread_mutex_lock(&rw_lock->stateMutex); + + /* Case 1 : rw_lock is Unlocked */ + if (rw_lock->is_locked_by_reader == false && + rw_lock->is_locked_by_writer == false) { + + assert(rw_lock->n_locks == 0); + assert(!rw_lock->is_locked_by_reader); + assert(!rw_lock->is_locked_by_writer); + assert(!rw_lock->writer_thread); + rw_lock->n_locks++; + rw_lock->is_locked_by_reader = true; + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + /* Case 2 : rw_lock is locked by reader(s) thread already ( could be same as this thread (recursive))*/ + + if (rw_lock->is_locked_by_reader) { + + assert(rw_lock->is_locked_by_writer == false); + assert(rw_lock->n_locks); + assert(!rw_lock->writer_thread); + rw_lock->n_locks++; + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + /* Case 3 : rw_lock is locked by a writer thread */ + while (rw_lock->is_locked_by_writer) { + + assert(rw_lock->n_locks); + assert(rw_lock->is_locked_by_reader == false); + assert(rw_lock->writer_thread); + rw_lock->n_reader_waiting++; + pthread_cond_wait(&rw_lock->cv, &rw_lock->stateMutex); + rw_lock->n_reader_waiting--; + + } + + if (rw_lock->n_locks == 0) { + /* First reader enter C.S */ + rw_lock->is_locked_by_reader = true; + } + + rw_lock->n_locks++; + + assert(rw_lock->is_locked_by_writer == false); + assert(!rw_lock->writer_thread); + + pthread_mutex_unlock(&rw_lock->stateMutex); +} + +void +rw_lock_wr_lock(rwlock_t* rw_lock) { + + pthread_mutex_lock(&rw_lock->stateMutex); + + + /* Case 1: rw_lock is Unlocked and free */ + if (rw_lock->is_locked_by_reader == false && + rw_lock->is_locked_by_writer == false) { + + assert(rw_lock->n_locks == 0); + assert(rw_lock->is_locked_by_reader == false); + assert(rw_lock->is_locked_by_writer == false); + assert(rw_lock->writer_thread); + + rw_lock->n_locks++; + rw_lock->is_locked_by_writer = true; + rw_lock->writer_thread = pthread_self(); + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + + /* Case 2 : if same thread tries to obtain write lock again, + Implement recursive property.*/ + + if (rw_lock->is_locked_by_reader && + rw_lock->writer_thread == pthread_self()) { + + assert(rw_lock->is_locked_by_reader == false); + assert(rw_lock->n_locks); + + rw_lock->n_locks++; + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + + /* Case 3 : If some other thread tries to obtain a lock on already + locked mutex*/ + + while (rw_lock->is_locked_by_reader || + rw_lock->is_locked_by_writer) { + + /* Sanity Check */ + if (rw_lock->is_locked_by_reader) { + + assert(rw_lock->is_locked_by_writer == false); + assert(rw_lock->writer_thread == 0); + assert(rw_lock->n_locks); + + } + else { + + assert(rw_lock->is_locked_by_reader == false); + assert(rw_lock->writer_thread); + assert(rw_lock->n_locks); + + } + + rw_lock->n_writer_waiting++; + pthread_cond_wait(&rw_lock->cv, &rw_lock->stateMutex); + rw_lock->n_writer_waiting--; + } + + /* Lock is Available now */ + + assert(rw_lock->is_locked_by_reader == false); + assert(rw_lock->is_locked_by_writer == false); + assert(rw_lock->n_locks == 0); + assert(!rw_lock->writer_thread); + rw_lock->is_locked_by_writer = true; + rw_lock->n_locks++; + rw_lock->writer_thread = pthread_self(); + + pthread_mutex_unlock(&rw_lock->stateMutex); +} + +void +rw_lock_unlock(rwlock_t* rw_lock) { + + pthread_mutex_lock(&rw_lock->stateMutex); + + /* Case 1 : Attempt to unlock the unlocked lock */ + assert(rw_lock->n_locks); + + /* Case 2 : Writer thread unlocking the rw_lock */ + if (rw_lock->is_locked_by_writer) { + + /* Only the owner of the rw_lock must attempt to unlock the rw_lock */ + assert(rw_lock->writer_thread == pthread_self()); + assert(rw_lock->is_locked_by_reader == false); + + rw_lock->n_locks--; + + if (rw_lock->n_locks) { + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + if (rw_lock->n_reader_waiting || + rw_lock->n_writer_waiting) { + pthread_cond_broadcast(&rw_lock->cv); + } + + rw_lock->is_locked_by_writer = false; + rw_lock->writer_thread = 0; + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + + } + /* Case 3 : Reader Thread trying to unlock the rw_lock */ + /* Note : Case 3 cannot be moved before Case 2. I leave it to you + for reasoning.. */ + + /* Ans to above ^ : Only one reader thread is being unlocked by case 3 + however, it is broadcasting the message to both reader or writer thread + and there may be some reader thread that still may be owing the rwlock, + writer thread may not be aware of it and cause problem. + So, Case 2 ensures that the rwlock is not being hold by any writer thread + as you can check it out with the condition of case.*/ + + /* There is minor design flaw in our implementation. Not actually flaw, but + a room to mis-use the rw_locks which is a trade off for simplicity. Here Simplicity is + that rw_lock implementation do not keep track of "who" all reader threads have + obtain the read lock on rw_lock. Here is an example, how rw_lock implementation + could be erroneously used by the developer, yet rw_lock implementation would not + report a bug ( assert () ). + Suppose, threads T1, T2 and T3 owns read locks on rw_lock. and Now, some + Thread T4 which do not own any type of lock on rw_lock invoke rw_lock_unlock( ) + API. The API would still honor the unlock request, since our rw_lock do not keep + track of who all "reader" threads owns the rw_lock and treat T4 as some reader thread + trying to unlock the rw_lock*/ + else if (rw_lock->is_locked_by_reader) { + + assert(rw_lock->is_locked_by_writer == false); + assert(rw_lock->writer_thread == 0); + + rw_lock->n_locks--; + + if (rw_lock->n_locks) { + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + if (rw_lock->n_reader_waiting || + rw_lock->n_writer_waiting) { + + pthread_cond_broadcast(&rw_lock->cv); + + } + + rw_lock->is_locked_by_reader = false; + pthread_mutex_unlock(&rw_lock->stateMutex); + return; + } + + assert(0); +} + +void +rw_lock_destroy(rwlock_t* rw_lock) { + + assert(rw_lock->n_locks == 0); + assert(rw_lock->n_reader_waiting == 0); + assert(rw_lock->n_writer_waiting == 0); + assert(rw_lock->is_locked_by_reader == false); + assert(rw_lock->is_locked_by_writer == false); + assert(!rw_lock->writer_thread); + pthread_mutex_destroy(&rw_lock->stateMutex); + pthread_cond_destroy(&rw_lock->cv); +} \ No newline at end of file diff --git a/Custom_read_write_lock/Custom_read_write_lock.h b/Custom_read_write_lock/Custom_read_write_lock.h new file mode 100644 index 0000000..9cb11bd --- /dev/null +++ b/Custom_read_write_lock/Custom_read_write_lock.h @@ -0,0 +1,55 @@ +#ifndef __RW_LOCK_H +#define __RW_LOCK_H + +#include +#include +#include + +typedef struct rwlock_{ + + /* A Mutex to manipulate/inspect the state of rwlock + in a mutually exclusive manner */ + + pthread_mutex_t stateMutex; + + /* CV to block the threads if rwlock is not available*/ + + pthread_cond_t cv; + + /* count of number of concurrent thread existing inside C.S*/ + uint16_t n_locks; + + /* No of reader thread waiting for the lock grant*/ + uint16_t n_reader_waiting; + + /* No of writer thread waiting for the lock grant*/ + uint16_t n_writer_waiting; + + /* Is locked currently occupied by Reader threads */ + bool is_locked_by_reader; + + /* Is locked currently occupied by Writer threads*/ + bool is_locked_by_writer; + + /* Thread handle of the writer thread currently holding the + lock. It is 0 if lock is not being held by any writer thread*/ + pthread_t writer_thread; +}rwlock_t; + + +void +rw_lock_init(rwlock_t* rw_lock); + +void +rw_lock_rd_lock(rwlock_t* rw_lock); + +void +rw_lock_wr_lock(rwlock_t* rw_lock); + +void +rw_lock_unlock(rwlock_t* rw_lock); + +void +rw_lock_destroy(rwlock_t* rw_lock); + +#endif \ No newline at end of file diff --git a/rwlock_example/rwlock_example.c b/rwlock_example/rwlock_example.c index 46776e0..78660b2 100644 --- a/rwlock_example/rwlock_example.c +++ b/rwlock_example/rwlock_example.c @@ -9,7 +9,7 @@ static int n_w = 0; pthread_mutex_t state_check_mutex; static void -cs_static_check() { +cs_status_check() { pthread_mutex_lock(&state_check_mutex); assert(n_r >= 0 && n_w >= 0); /* cannot be negative */