Good day!
After a more detailed analysis of the task, it turned out that the most appropriate synchronization element in my case would be a read lock, with the possibility of capturing the write lock, since the design contains functions that: 1) only read the table, 2) just write the table, 3) sequentially, logically inextricably read and write the table (for example, searching for an element with its subsequent deletion). Based on the avp example from my previous question, thanks again, I wrote my own version of the implementation of such a lock based on the rwlock posix-primitive.
I would like to discuss the advantages and disadvantages.
Lock implementation:
#include "my_rwlock.h" static pthread_mutex_t lock_protect_mutex = PTHREAD_MUTEX_INITIALIZER; //ΠΈΠ΄Π΅Π½ΡΠΈΡΠΈΠΊΠ°ΡΠΎΡ ΠΏΠΎΡΠΎΠΊΠ°, Π·Π°Ρ
Π²Π°ΡΠΈΠ²ΡΠ΅Π³ΠΎ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ Ρ Π΄ΠΎΠ·Π°Ρ
Π²Π°ΡΠΎΠΌ static pthread_mutex_t tid_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_t protect_tid; int smart_rwlock_operation(p_rwlock, operation) pthread_rwlock_t *p_rwlock; uint8_t operation; { int res = 0; int ok = 0; if(p_rwlock == (pthread_rwlock_t *)NULL) return(EINVAL); switch(operation){ case MY_RDLOCK ://ΠΎΠ±ΡΡΠ½Π°Ρ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠ° Π½Π° ΡΡΠ΅Π½ΠΈΠ΅ res = pthread_rwlock_rdlock(p_rwlock); #ifdef DEBUG if(res) printf("Error %d of locking rwlock (pthread %u)\n", res,pthread_self()); #endif break; case MY_WRLOCK ://ΠΎΠ±ΡΡΠ½Π°Ρ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠ° Π½Π° Π·Π°ΠΏΠΈΡΡ res = pthread_rwlock_wrlock(p_rwlock); #ifdef DEBUG if(res) printf("Error %d of locking rwlock (pthread %u)\n", res,pthread_self()); #endif break; case MY_UNLOCK ://ΡΠ½ΡΡΠΈΠ΅ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΈ res = pthread_rwlock_unlock(p_rwlock); #ifdef DEBUG if(res) printf("Error %d of unlocking rwlock (pthread %u)\n", res,pthread_self()); #endif break; case MY_RDLOCK_PROTECT ://Π·Π°Ρ
Π²Π°Ρ Π½Π° ΡΡΠ΅Π½ΠΈΠ΅ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΈ Ρ Π΄ΠΎΠ·Π°Ρ
Π²Π°ΡΠΎΠΌ res = pthread_mutex_lock(&lock_protect_mutex); if(!res){ // ΠΌΡΡΡΠ΅ΠΊΡ Π·Π°Ρ
Π²Π°ΡΠ΅Π½, Π·Π°Ρ
Π²Π°ΡΡΠ²Π°Π΅ΠΌ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ ΡΡΠ΅Π½ΠΈΡ res = pthread_rwlock_rdlock(p_rwlock); if(!res){ //Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠ° Π·Π°Ρ
Π²Π°ΡΠ΅Π½Π° pthread_mutex_lock(&tid_mutex); protect_tid = pthread_self(); pthread_mutex_unlock(&tid_mutex); } else{ //ΠΎΡΠΈΠ±ΠΊΠ° - ΠΎΡΠΏΡΡΡΠΈΠΌ ΠΌΡΡΡΠ΅ΠΊΡ res = pthread_mutex_unlock(&lock_protect_mutex); } } #ifdef DEBUG if(res) printf("Error %d of protected read-locking (pthread %u)\n",res,pthread_self()); #endif break; case MY_UNLOCK_PROTECT ://ΡΠ½ΡΡΠΈΠ΅ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΈ Ρ Π΄ΠΎΠ·Π°Ρ
Π²Π°ΡΠΎΠΌ pthread_mutex_lock(&tid_mutex); if((res = (pthread_mutex_trylock(&lock_protect_mutex)) == EBUSY) && (pthread_equal(pthread_self(),protect_tid) != 0)){ //ΠΌΡΡΡΠ΅ΠΊΡ ΡΠΆΠ΅ Π·Π°Ρ
Π²Π°ΡΠ΅Π½ ΡΡΠΈΠΌ ΠΆΠ΅ ΠΏΠΎΡΠΎΠΊΠΎΠΌ ok = 1; } else if(!res){ //ΠΌΡΡΡΠ΅ΠΊΡ Π·Π°Ρ
Π²Π°ΡΠ΅Π½, Π½Π΅ΠΊΠΎΡΡΠ΅ΠΊΡΠ½Π°Ρ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ pthread_mutex_unlock(&lock_protect_mutex); res = EINVAL; } else //ΠΌΡΡΡΠ΅ΠΊΡ Π·Π°Ρ
Π²Π°ΡΠ΅Π½ Π΄ΡΡΠ³ΠΈΠΌ ΠΏΠΎΡΠΎΠΊΠΎΠΌ, Π½Π΅ΠΊΠΎΡΡΠ΅ΠΊΡΠ½Π°Ρ ΠΎΠΏΠ΅ΡΠ°ΡΠΈΡ res = EBUSY; pthread_mutex_unlock(&tid_mutex); if(ok){ //ΠΌΡ Π² Π½ΡΠΆΠ½ΠΎΠΌ ΠΏΠΎΡΠΎΠΊΠ΅, ΠΌΠΎΠΆΠ½ΠΎ ΠΎΡΠ²ΠΎΠ±ΠΎΠ΄ΠΈΡΡ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ ΠΈ ΠΌΡΡΡΠ΅ΠΊΡ res = pthread_rwlock_unlock(p_rwlock); if(!res) res = pthread_mutex_unlock(&lock_protect_mutex); else pthread_mutex_unlock(&lock_protect_mutex); } #ifdef DEBUG if(res) printf("Error %d of protected unlocking (pthread %u)\n",res,pthread_self()); #endif break; case MY_FROMRD_TOWR_PROTECT ://Π΄ΠΎΠ·Π°Ρ
Π²Π°Ρ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΈ Π½Π° ΡΡΠ΅Π½ΠΈΠ΅ Π΄ΠΎ Π·Π°ΠΏΠΈΡΠΈ pthread_mutex_lock(&tid_mutex); if((pthread_mutex_trylock(&lock_protect_mutex) == EBUSY) && (pthread_equal(pthread_self(),protect_tid) != 0)){ ok = 1; } else if(!res){ pthread_mutex_unlock(&lock_protect_mutex); res = EINVAL; } else res = EBUSY; pthread_mutex_unlock(&tid_mutex); if(ok){ //Π΄ΠΎΠ·Π°Ρ
Π²Π°ΡΡΠ²Π°Π΅ΠΌ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ Π½Π° Π·Π°ΠΏΠΈΡΡ res = pthread_rwlock_unlock(p_rwlock); if(!res) res = pthread_rwlock_wrlock(p_rwlock); } #ifdef DEBUG if(res) printf("Error %d of update to write-locking (pthread %u)\n",res,pthread_self()); #endif break; case MY_FROMWR_TORD_PROTECT ://ΠΎΡΠΎΠ²Π±ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΈ Π½Π° Π·Π°ΠΏΠΈΡΡ Π΄ΠΎ ΡΡΠ΅Π½ΠΈΡ pthread_mutex_lock(&tid_mutex); if((pthread_mutex_trylock(&lock_protect_mutex) == EBUSY) && (pthread_equal(pthread_self(),protect_tid) != 0)){ ok = 1; } else if(!res){ pthread_mutex_unlock(&lock_protect_mutex); res = EINVAL; } else res = EBUSY; pthread_mutex_unlock(&tid_mutex); if(ok){ //ΠΎΡΠΊΠ°ΡΡΠ²Π°Π΅ΠΌΡΡ ΠΊ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠ΅ ΡΡΠ΅Π½ΠΈΡ res = pthread_rwlock_unlock(p_rwlock); if(!res) res = pthread_rwlock_rdlock(p_rwlock); } #ifdef DEBUG if(res) printf("Error %d of update to read-locking (pthread %u)\n",res,pthread_self()); #endif break; case MY_WRLOCK_PROTECT : res = pthread_mutex_lock(&lock_protect_mutex); if(!res){ // ΠΌΡΡΡΠ΅ΠΊΡ Π·Π°Ρ
Π²Π°ΡΠ΅Π½, Π·Π°Ρ
Π²Π°ΡΡΠ²Π°Π΅ΠΌ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ Π·Π°ΠΏΠΈΡΠΈ res = pthread_rwlock_wrlock(p_rwlock); if(!res){ //Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠ° Π·Π°Ρ
Π²Π°ΡΠ΅Π½Π° pthread_mutex_lock(&tid_mutex); protect_tid = pthread_self(); pthread_mutex_unlock(&tid_mutex); } else{ //ΠΎΡΠΈΠ±ΠΊΠ° - ΠΎΡΠΏΡΡΡΠΈΠΌ ΠΌΡΡΡΠ΅ΠΊΡ res = pthread_mutex_unlock(&lock_protect_mutex); } } #ifdef DEBUG if(res) printf("Error %d of protected write-locking (pthread %u)\n",res,pthread_self()); #endif break; default : return(EINVAL); break; } return(res); }
Test:
//my_rwlock.h
#ifndef __MY_RWLOCK__ #define __MY_RWLOCK__ #include <stdlib.h> #include <stdio.h> #include <sys/types.h> #include <strings.h> #include <errno.h> #include <pthread.h> #define CNT_EL 5 #define BUF_LEN 256 #define MAX_CNT 20 // ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΈ Π½Π°Π΄ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΠΎΠΉ ΡΡΠ΅Π½ΠΈΡ-Π·Π°ΠΏΠΈΡΠΈ Ρ Π΄ΠΎΠ·Π°Ρ
Π²Π°ΡΠΎΠΌ #define MY_RDLOCK 0x00 #define MY_WRLOCK 0x01 #define MY_UNLOCK 0x02 #define MY_RDLOCK_PROTECT 0x03 #define MY_UNLOCK_PROTECT 0x04 #define MY_FROMRD_TOWR_PROTECT 0x05 #define MY_FROMWR_TORD_PROTECT 0x06 #define MY_WRLOCK_PROTECT 0x07 int array[CNT_EL]; int cnt; pthread_rwlock_t array_lock; int smart_rwlock_operation(pthread_rwlock_t *, uint8_t ); #endif //__MY_RWLOCK__
//main.c
#include "my_rwlock.h" void *func_reader(void *arg){ //ΡΡΠ½ΠΊΡΠΈΡ ΡΠΈΡΠ°ΡΠ΅Π»Ρ int i; while(1){ smart_rwlock_operation(&array_lock, MY_RDLOCK); if(cnt >= MAX_CNT){ smart_rwlock_operation(&array_lock, MY_UNLOCK); break; } printf("Read with simple rdlock : "); for(i = 0; i < CNT_EL; i++) printf("%d ",array[i]); printf("\n"); smart_rwlock_operation(&array_lock, MY_UNLOCK); sleep(1); } return((void *)0); } void *func_writer(void *arg){ //ΡΡΠ½ΠΊΡΠΈΡ ΠΏΠΈΡΠ°ΡΠ΅Π»Ρ int i; while(1){ smart_rwlock_operation(&array_lock, MY_WRLOCK_PROTECT); if(cnt >= MAX_CNT){ smart_rwlock_operation(&array_lock, MY_UNLOCK_PROTECT); break; } cnt++; printf("Write with simple wrlock : "); for(i = 0; i < CNT_EL; i++){ array[i] = i+10; printf("%d ",array[i]); } printf("\n"); smart_rwlock_operation(&array_lock, MY_UNLOCK_PROTECT); sleep(2); } return((void *)0); } void *func_smart_reader(void *arg){ //ΡΡΠ½ΠΊΠΈΡΠΈΡ ΡΠΈΡΠ°ΡΠ΅Π»Ρ, ΠΊΠΎΡΠΎΡΡΠΉ ΠΌΠΎΠΆΠ΅Ρ ΠΏΡΠΎΠΊΠ°ΡΠ°ΡΡΡΡ Π΄ΠΎ ΠΏΠΈΡΠ°ΡΠ΅Π»Ρ int i; while(1){ smart_rwlock_operation(&array_lock, MY_RDLOCK_PROTECT); if(cnt >= MAX_CNT){ smart_rwlock_operation(&array_lock, MY_UNLOCK_PROTECT); break; } //Π·Π°Ρ
Π²Π°ΡΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ ΡΡΠ΅Π½ΠΈΡ Ρ Π²ΠΎΠ·ΠΌΠΎΠΆΠ½ΠΎΡΡΡΡ Π΄ΠΎΠ·Π°Ρ
Π²Π°ΡΠ° Π½Π° Π·Π°ΠΏΠΈΡΡ printf("Read with smart rwlock : "); for(i = 0; i < CNT_EL; i++) printf("%d ",array[i]); printf("\n"); if(array[0] != 0){ //Π²Π΅ΡΠ½Π΅ΠΌ ΡΡΠ°ΡΡΠΉ ΠΌΠ°ΡΡΠΈΠ² smart_rwlock_operation(&array_lock, MY_FROMRD_TOWR_PROTECT); //ΠΏΡΠΎΠ°ΠΏΠ³ΡΠ΅ΠΉΠ΄ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ ΡΡΠ΅Π½ΠΈΡ Π΄ΠΎ Π·Π°ΠΏΠΈΡΠΈ cnt++; printf("Write with smart rwlock : "); for(i = 0; i < CNT_EL; i++){ array[i] = i; printf("%d ",array[i]); } printf("\n"); smart_rwlock_operation(&array_lock, MY_FROMWR_TORD_PROTECT); // ΡΠ½ΠΎΠ²Π° ΠΏΠΎΠ½ΠΈΠ·ΠΈΠ»ΠΈ Π±Π»ΠΎΠΊΠΈΡΠΎΠ²ΠΊΡ Π΄ΠΎ ΡΡΠ΅Π½ΠΈΡ } printf("Affordable to read\n"); smart_rwlock_operation(&array_lock, MY_UNLOCK_PROTECT); sleep(1); } return((void *)0); } int main(int argc, char *argv[]) { int i; pthread_t tid[4]; char buf[BUF_LEN]; void *res; array_lock = PTHREAD_MUTEX_INITIALIZER; for(i = 0;i < CNT_EL; i++) array[i] = i; cnt = 0; if(pthread_create(&tid[0],NULL,func_reader,NULL) != 0){ bzero(buf,BUF_LEN); strerror_r(errno,buf,BUF_LEN); printf("Error of thread createng : %s\n",buf); } if(pthread_create(&tid[1],NULL,func_reader,NULL) != 0){ bzero(buf,BUF_LEN); strerror_r(errno,buf,BUF_LEN); printf("Error of thread createng : %s\n",buf); } if(pthread_create(&tid[2],NULL,func_writer,NULL) != 0){ bzero(buf,BUF_LEN); strerror_r(errno,buf,BUF_LEN); printf("Error of thread createng : %s\n",buf); } if(pthread_create(&tid[3],NULL,func_smart_reader,NULL) != 0){ bzero(buf,BUF_LEN); strerror_r(errno,buf,BUF_LEN); printf("Error of thread createng : %s\n",buf); } for(i = 0; i < 4; i++) pthread_join(tid[i], &res); printf("Bye!!!\n"); return 0; }
Results :
...
Read with simple rdlock : Read with simple rdlock : 0 1 2 3 4 Read with smart rwlock : 0 1 2 3 4 Affordable to read 0 1 2 3 4 Write with simple wrlock : 10 11 12 13 14 Read with simple rdlock : 10 11 12 13 14 Read with simple rdlock : 10 11 12 13 14 Read with smart rwlock : 10 11 12 13 14 Write with smart rwlock : 0 1 2 3 4 Affordable to read Read with simple rdlock : 0 1 2 3 4 Read with smart rwlock : 0 1 2 3 4 Affordable to read Read with simple rdlock : 0 1 2 3 4 Write with simple wrlock : 10 11 12 13 14 Read with simple rdlock : 10 Read with simple rdlock : 11 12 13 14 10 11 12 13 14 Read with smart rwlock : 10 11 12 13 14 Write with smart rwlock : 0 1 2 3 4 Affordable to read Read with simple rdlock : 0 Read with smart rwlock : 0 1 2 3 4 Affordable to read Read with simple rdlock : 0 1 2 3 4 1 2 3 4 Write with simple wrlock : 10 11 12 13 14 Read with simple rdlock : 10 11 12 13 14 Read with smart rwlock : 10 11 12 13 14 Read with simple rdlock : 10 11 12 13 14 Write with smart rwlock : 0 1 2 3 4 Affordable to read Read with simple rdlock : 0 1 Read with simple rdlock : 0 Read with smart rwlock : 0 1 1 2 3 4 Affordable to read 2 3 4 2 3 4
...
* I did not specifically block the output operation so that it could be seen that the reader threads are working simultaneously.
UPDATE
It is implied that MY_RDLOCK_PROTECT
used only when we need to capture a read-write read-write (there is a similar primitive in Boost), it will be used only in the third variant, when reading and writing are logically related. In functions where only reading is MY_RDLOCK
, MY_RDLOCK
used, in it the lock_protect_mutex
mutex lock_protect_mutex
not performed. Thus, they will work at the same time - all the threads that captured the lock using MY_RDLOCK
, and the threads that captured MY_RDLOCK_PROTECT
, until they upgrade the read-to-write lock using MY_FROMRD_TOWR_PROTECT
.
When a read lock is captured for writing via MY_FROMRD_TOWR_PROTECT
, "writers only" cannot get into this process, since lock_protect_mutex
is still captured.
The streams are βwriters onlyβ, they will first wait for the lock_protect_mutex
mutex to be lock_protect_mutex
, and then wait for βonly readersβ to remain. At the same time, since the mutex is already captured, threads that have a mixed version of "read + write" will wait for the completion of the "only writer" work.
To summarize, I wanted to achieve the following: βonly writersβ and βwriter + readerβ never work at the same time, while βonly readersβ and βwriter + readerβ work simultaneously in the period when βwriter + readerβ has not produced dozakvat lock before recording, or on the contrary, lowered it from writing to reading.
MY_RDLOCK_PROTECT
looksMY_RDLOCK_PROTECT
. I do not understand how to use it. There after alllock_protect_mutex
blocked , and then on reading rwlockp_rwlock
. Accordingly, any other thread will not be able to take rwlockp_rwlock
in this way (it will wait forlock_protect_mutex
), and the same one will simply go to deadlock (although just with rwlockp_rwlock
should stay working). Then what is the meaning of the operationMY_RDLOCK_PROTECT
? - You would briefly describe in what situation what operations are you going to use and how they are combined. - avp