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.

  • @margosh, until I figured it out, I just looked and MY_RDLOCK_PROTECT looks MY_RDLOCK_PROTECT . I do not understand how to use it. There after all lock_protect_mutex blocked , and then on reading rwlock p_rwlock . Accordingly, any other thread will not be able to take rwlock p_rwlock in this way (it will wait for lock_protect_mutex ), and the same one will simply go to deadlock (although just with rwlock p_rwlock should stay working). Then what is the meaning of the operation MY_RDLOCK_PROTECT ? - You would briefly describe in what situation what operations are you going to use and how they are combined. - avp
  • @ avp, added an explanation of logic - margosh

1 answer 1

@margosh, read the explanation. From the last paragraph, much has become clear. Now we are not talking about nested locks (the previous question ).

I think you have taken into account (just did not write about it) that at least MY_RDLOCK_PROTECT and MY_WRLOCK_PROTECT cannot be used in smart_rwlock_operation() with different pthread_rwlock_t *p_rwlock in different threads, since lock on the same lock_protect_mutex . Those. parallel processing of different independent data structures with this code is impossible.

I do not understand why in MY_UNLOCK_PROTECT you do not reset protect_tid (the previous value remains, this can not lead to a conflict?).

In MY_FROMRD_TOWR_PROTECT and MY_FROMWR_TORD_PROTECT in code

 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; 

I think I need to write

 if(((res = pthread_mutex_trylock(&lock_protect_mutex)) == EBUSY) && .... 

If something else comes to mind, then I will add the answer.

@margosh, and in general, with the upcoming March 8.

  • @avp, thanks :) yes, smart_rwlock_operation () - for synchronizing one global data table. In general, initially protect_tid in MY_UNLOCK_PROTECT cleared, but then decided that there was no point, since the value is assigned only when lock_protect_mutex is captured, i.e. after performing MY_UNLOCK_PROTECT, the logic must be either MY_WRLOCK_PROTECT or MY_RDLOCK_PROTECT, and nothing is checked in them after the primitive is captured, only the new protect_tid value is set. Correct me if I did not consider something. Oh , about the "res" - you are undoubtedly right - margosh
  • @margosh, I agree, without zeroing protect_tid this logic also works. It can only be, for clarity, to allocate all PROTECT operations into a separate function without the pthread_rwlock_t *p_rwlock , just this lock object of one global data table should be visible from the function. Then a number of questions that arise when reading the code will immediately disappear. - avp