@margosh , for some reason I don’t see any special problems after this description.
The only thing is that when you get wrlock, you need to check if the item has already been removed by another thread? And all functions should work by performing this check.
IMHO in any case can not do without it, because You cannot "raise" the lock level from READ to WRITE. All n rdlock (if there were several of them in the stream), in any case, should be released before wrlock.
And perhaps this solution will do. Of course, it works only in the case when the flow for further work does not matter whether the item is actually removed.
Search functions (streams) make an rdlock to the list. Found items (candidates for deletion) are placed in a certain set (they remain in the list, put a pointer or some item ID in the set). Naturally, a mutex and a semaphore are associated with it, on which the stream hangs, which will delete the elements. That he does wrlock.
Something like that.
UPDATE
@margosh , I don’t know for sure if you would like to see such a code, but I decided to approach the nested wrlock quite formally and I hope this will be useful for you.
The lock22()
function can be called already having a lock, the number of unlock22()
calls must match the number of nested lock22()
calls lock22()
. To exit from all levels of nesting and loss of lock, call force_unlock22()
.
The address of the struct lock22
structure that contains the pthread_rwlock_t lock
is passed to all functions. The structure must be initialized before use by calling init_lock22()
.
It is important . All other calls to pthread_rwlock_rdlock()
and other similar functions must refer to this particular lock field in struct lock22
.
// lock22.c avp 2013 nested pthread_rwlock_wrlock, pthread_rwlock_unlock #include <stdio.h> #include <stdlib.h> #include <pthread.h> /* #include "lock22.h" put next lines to lock22.h */ struct lock22 { pthread_t owner; int cnt; pthread_rwlock_t lock; }; extern int init_lock22 (struct lock22 *p, const pthread_rwlockattr_t *a), lock22 (struct lock22 *p), unlock22 (struct lock22 *p), force_unlock22 (struct lock22 *p); /* end of "lock22.h" */ // returns 0 if OK int init_lock22 (struct lock22 *p, const pthread_rwlockattr_t *a) { p->owner = 0; p->cnt = 0; return pthread_rwlock_init(&p->lock, a); } static pthread_mutex_t locki = PTHREAD_MUTEX_INITIALIZER; // returns 0 if OK int lock22 (struct lock22 *p) { int rc = pthread_mutex_lock(&locki); if (rc) return rc; pthread_t self = pthread_self(); if (!pthread_equal(p->owner, self)) { // try to acquire wrlock pthread_mutex_unlock(&locki); #if TEST printf ("CNT %d owner %p self %p\n", p->cnt, (void *)p->owner, (void *)self); #endif if (rc = pthread_rwlock_wrlock(&p->lock)) return rc; // acquired wrlock p->cnt = 1; p->owner = self; } else { // already has wrlock p->cnt++; rc = pthread_mutex_unlock(&locki); } return rc; } // returns 0 if OK int unlock22 (struct lock22 *p) { int rc = pthread_mutex_lock(&locki); if (rc) return rc; pthread_t self = pthread_self(); if (!pthread_equal(p->owner, self)) { pthread_mutex_unlock(&locki); return -1; // logical error. Only owner thread can call unlock22() } if (--p->cnt == 0) { // last nested lock rc = pthread_rwlock_unlock(&p->lock); p->owner = 0; } pthread_mutex_unlock(&locki); return rc; } // returns 0 if OK int force_unlock22 (struct lock22 *p) { int rc = pthread_mutex_lock(&locki); if (rc) return rc; pthread_t self = pthread_self(); if (!pthread_equal(p->owner, self)) { pthread_mutex_unlock(&locki); return -1; // logical error. Only owner thread can call unlock22() } rc = pthread_rwlock_unlock(&p->lock); p->owner = 0; p->cnt = 0; pthread_mutex_unlock(&locki); return rc; } #if TEST /******************************************************* Testing code *******************************************************/ struct lock22 l22; void test (char *msg) { printf ("%s lock22() %d\n", msg, lock22(&l22)); printf ("%s cnt = %d\n", msg, l22.cnt); printf ("%s unlock22() %d\n", msg, unlock22(&l22)); printf ("%s cnt = %d\n", msg, l22.cnt); } void * ptest (void *a) { puts("Thread go"); test("ptest1"); printf ("ptest lock22() %d\n", lock22(&l22)); printf ("cnt = %d\n", l22.cnt); test("ptest2-nested"); test("ptest3-nested"); printf ("ptest unlock22() %d\n", unlock22(&l22)); printf ("cnt = %d\n", l22.cnt); puts("Thread exit"); } int main (int ac, char *av[]) { pthread_t th; void *ptres; printf ("init_lock22(): %d\n", init_lock22(&l22, NULL)); test("main1"); test("main2"); printf ("main lock22() %d\n", lock22(&l22)); printf ("cnt = %d\n", l22.cnt); pthread_create(&th, NULL, ptest, NULL); puts ("Enter for continue"); getchar(); test("main3-nested"); test("main4-nested"); printf ("main unlock22() %d\n", unlock22(&l22)); printf ("main cnt = %d ???\n", l22.cnt); pthread_join(th, NULL); printf ("fin cnt = %d\n", l22.cnt); puts("End"); } #endif
Made in ubuntu, not carefully tested.
Broadcast in ./a.out
gcc lock22.c -pthread -DTEST
If something is not clear, ask.
UPDATE-2
I init_lock22()
that for normal use in init_lock22()
you need to add an argument - attributes of the locale. Then man pthread_rwlock_init
will clearly correspond.
The code above corrected.
lock_22(), unlock_22()
(I don’t know what to call it) that will safely make a nested lock. By the way, do you really have this nested (balanced) lock-unlock, or maybe several lock (do you really need the first one) and only one unlock? If in principle such a solution is suitable, then I can try to sketch. - avp