mirror of
https://github.com/Hizenberg469/MultiThreading_Part_A.git
synced 2026-04-19 18:12:24 +03:00
Custom rwlock data structure
This commit is contained in:
21
Custom_read_write_lock/CMakeLists.txt
Normal file
21
Custom_read_write_lock/CMakeLists.txt
Normal file
@@ -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 "$<IF:$<AND:$<C_COMPILER_ID:MSVC>,$<CXX_COMPILER_ID:MSVC>>,$<$<CONFIG:Debug,RelWithDebInfo>:EditAndContinue>,$<$<CONFIG:Debug,RelWithDebInfo>: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.
|
||||
101
Custom_read_write_lock/CMakePresets.json
Normal file
101
Custom_read_write_lock/CMakePresets.json
Normal file
@@ -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}"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
243
Custom_read_write_lock/Custom_read_write_lock.c
Normal file
243
Custom_read_write_lock/Custom_read_write_lock.c
Normal file
@@ -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);
|
||||
}
|
||||
55
Custom_read_write_lock/Custom_read_write_lock.h
Normal file
55
Custom_read_write_lock/Custom_read_write_lock.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#ifndef __RW_LOCK_H
|
||||
#define __RW_LOCK_H
|
||||
|
||||
#include <pthread.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
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
|
||||
@@ -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 */
|
||||
|
||||
Reference in New Issue
Block a user