gngrwzrd
gngrwzrd

Reputation: 6012

Deallocating memory in multi-threaded environment

I'm having a hard time figuring out how to manage deallocation of memory in multithreaded environments. Specifically what I'm having a hard time with is using a lock to protect a structure, but when it's time to free the structure, you have to unlock the lock to destroy the lock itself. Which will cause problems if a separate thread is waiting on that same lock that you need to destroy.

I'm trying to come up with a mechanism that has retain counts, and when the object's retain count is 0, it's all freed. I've been trying a number of different things but just can't get it right. As I've been doing this it seems like you can't put the locking mechanism inside of the structure that you need to be able to free and destroy, because that requires you unlock the the lock inside of it, which could allow another thread to proceed if it was blocked in a lock request for that same structure. Which would mean that something undefined is guaranteed to happen - the lock was destroyed, and deallocated so either you get memory access errors, or you lock on undefined behavior..

Would someone mind looking at my code? I was able to put together a sandboxed example that demonstrates what I'm trying without a bunch of files.

http://pastebin.com/SJC86GDp

#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

struct xatom {
    short rc;
    pthread_rwlock_t * rwlck;
};
typedef struct xatom xatom;

struct container {
    xatom * atom;
};
typedef struct container container;

#define nr 1
#define nw 2
pthread_t readers[nr];
pthread_t writers[nw];

container * c;

void  retain(container * cont);
void  release(container ** cont);
short retain_count(container * cont);

void * rth(void * arg) {
    short rc;
    while(1) {
        if(c == NULL) break;
        rc = retain_count(c);
    }
    printf("rth exit!\n");
    return NULL;
}

void * wth(void * arg) {
    while(1) {
        if(c == NULL) break;
        release((container **)&c);
    }
    printf("wth exit!\n");
    return NULL;
}

short retain_count(container * cont) {
    short rc = 1;
    pthread_rwlock_rdlock(cont->atom->rwlck);
    printf("got rdlock in retain_count\n");
    rc = cont->atom->rc;
    pthread_rwlock_unlock(cont->atom->rwlck);
    return rc;
}

void retain(container * cont) {
    pthread_rwlock_wrlock(cont->atom->rwlck);
    printf("got retain write lock\n");
    cont->atom->rc++;
    pthread_rwlock_unlock(cont->atom->rwlck);
}

void release(container ** cont) {
    if(!cont || !(*cont)) return;
    container * tmp = *cont;
    pthread_rwlock_t ** lock = (pthread_rwlock_t **)&(*cont)->atom->rwlck;
    pthread_rwlock_wrlock(*lock);
    printf("got release write lock\n");
    if(!tmp) {
        printf("return 2\n");
        pthread_rwlock_unlock(*lock);
        if(*lock) {
            printf("destroying lock 1\n");
            pthread_rwlock_destroy(*lock);
            *lock = NULL;
        }
        return;
    }
    tmp->atom->rc--;
    if(tmp->atom->rc == 0) {
        printf("deallocating!\n");
        *cont = NULL;
        pthread_rwlock_unlock(*lock);
        if(pthread_rwlock_trywrlock(*lock) == 0) {
            printf("destroying lock 2\n");
            pthread_rwlock_destroy(*lock);
            *lock = NULL;
        }
        free(tmp->atom->rwlck);
        free(tmp->atom);
        free(tmp);
    } else {
        pthread_rwlock_unlock(*lock);
    }
}

container * new_container() {
    container * cont = malloc(sizeof(container));
    cont->atom = malloc(sizeof(xatom));
    cont->atom->rwlck = malloc(sizeof(pthread_rwlock_t));
    pthread_rwlock_init(cont->atom->rwlck,NULL);
    cont->atom->rc = 1;
    return cont;
}

int main(int argc, char ** argv) {
    c = new_container();
    int i = 0;
    int l = 4;
    for(i=0;i<l;i++) retain(c);
    for(i=0;i<nr;i++) pthread_create(&readers[i],NULL,&rth,NULL);
    for(i=0;i<nw;i++) pthread_create(&writers[i],NULL,&wth,NULL);
    sleep(2);
    for(i=0;i<nr;i++) pthread_join(readers[i],NULL);
    for(i=0;i<nw;i++) pthread_join(writers[i],NULL);
    return 0;
}

Thanks for any help!

Upvotes: 1

Views: 580

Answers (1)

SF.
SF.

Reputation: 14049

Yes, you can't put the key inside the safe. Your approach with refcount (create object when requested and doesn't exist, delete on last release) is correct. But the lock must exist at least a moment before object is created and after it is destroyed - that is, while it is used. You can't delete it from inside of itself.

OTOH, you don't need countless locks, like one for each object you create. One lock that excludes obtaining and releasing of all objects will not create much performance loss at all. So just create the lock on init and destroy on program end. Otaining/releasing an object should take short enough that lock on variable A blocking access to unrelated variable B should almost never happen. If it happens - you can still introduce one lock per all rarely obtained variables and one per each frequently obtained one.

Also, there seems to be no point for rwlock, plain mutex suffices, and the create/destroy operations MUST exclude each other, not just parallel instances of themselves - so use pthread_create_mutex() family instead.

Upvotes: 1

Related Questions