Ich schreibe gerade ein Programm, das sehr oft malloc braucht, um structs zu erzeugen. Da die erzeugten Objekte an verschiedenen Stellen gebraucht werden, habe ich einen einen retain count implementiert. Um den Retain Count zu inkrementieren oder dekrementieren benutze ich Atomic Builtins, die GCC 4.1 und später zur verfügung stellt. Der Vorteil gegenüber Mutex ist hauptsächlich die Geschwindigkeit. Das Problem ist jetzt, dass es laut Dokumentation (siehe obigen Link) Prozessoren gibt, die diese Funktionen nicht unterstützen:
Wie kann ich jetzt in diesen Funktionen den Zugriff auf ptr sinnvoll sperren?
Bisher hatte ich folgende Idee:
Ich benutze eine statische Mutexvariable, die jedesmal gesperrt wird, wenn release oder retain aufgerufen werden. Der Nachteil ist, dass jeweils nur ein Objekt bearbeitet werden kann. Das ist aber sehr ineffizient.
Weiss jemand wie ich das Problem lösen kann?
Hier ist noch der Code:
EDIT: Ich habe den Code besser kommentiert und ein bisschen verändert.
So wie ich die Dokumentation verstanden habe, wird dann eine Funktion aufgerufen, die von dem Programmierer (also mir) implementiert werden muss (__sync_sub_and_fetch_4(int *ptr, int value) bzw. __sync_fetch_and_add_4(int *ptr, int value) siehe Implementation wieter unten).Not all operations are supported by all target processors. If a particular operation cannot be implemented on the target processor, a warning will be generated and a call an external function will be generated. The external function will carry the same name as the builtin, with an additional suffix `_n' where n is the size of the data type.
Wie kann ich jetzt in diesen Funktionen den Zugriff auf ptr sinnvoll sperren?
Bisher hatte ich folgende Idee:
Ich benutze eine statische Mutexvariable, die jedesmal gesperrt wird, wenn release oder retain aufgerufen werden. Der Nachteil ist, dass jeweils nur ein Objekt bearbeitet werden kann. Das ist aber sehr ineffizient.
Weiss jemand wie ich das Problem lösen kann?
Hier ist noch der Code:
Code:
/*
* Usage of ObjectHeader:
* To get the support of reference count for you own objects, place an ObjectHeader object as the first entry in your struct.
* Example:
* struct MyObject {
* ObjectHeader header;
* ... // Whatever you need for your struct comes here
* }
*
* If you have done that, you can call retain and release on your objects.
* NOTICE: NEVER USE retain, release OR retainCount ON OBJECTS THAT DON'T SUPPORT RETAIN COUNT!
*/
#ifndef OBJECT_HEADER
#define OBJECT_HEADER
#include <pthread.h>
typedef void (*DeallocCallback) (void *object);
/*
* Do not access this structure directly. Use objectHeaderInitialize to initialize and retain, release and retainCount for interactin with the reference count.
*/
typedef struct _ObjectHeader {
int referenceCount;
DeallocCallback callback;
pthread_mutex_t retainCountMutex;
} ObjectHeader;
/*
* Initializes the object passed with header for use in a structure. The reference count will be set to 1.
* If your compiler doesn't support builtin atomic functions, a mutex object will be initialized for header.
* Do not initialize the same header object more than once, it can lead to inconsistencies and memory leacks.
* Arguments:
* header: pointer to the object header that should be initialized.
* callback: The function that should be called when an object should be deallocated ( when the retain count reaches 0).
* Return Value:
* 0 on success
* -1 if the mutex couldn't be created
*/
int objectHeaderInitialize(ObjectHeader *header, DeallocCallback callback);
/*
* retain increases the reference count by 1 and returns the retained object.
* Arguments:
* object: the object that should be retained
* Return Value:
* the retained object.
*/
void *retain(void *object); // Increment the retain count
/*
* release decreases the reference count by 1. If the retain count reaches 0, it will call the dealloc callback.
* Arguments:
* object: the object that should be released
*/
void release(void *object);
/*
* Returns the objects reference count.
* Arguments:
* object: the object that should be retained
* Return Value:
* The objects reference count.
*/
int retainCount(void *object);
#endif
Code:
#include "ObjectHeader.h"
#include <stdio.h>
#include <stdlib.h>
#define GCC_VERSION 0 /* Set default GCC_VERSION to 0, in case the compiler isn't gcc */
#ifdef __GNUC__
# undef GCC_VERSION
# define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
#endif
/*
* To support atomic operations, gcc 4.1.2 or an intel compiler is required
*/
#define SUPPORT_FOR_ATOMIC ((GCC_VERSION >= 40100) || defined(__INTEL_COMPILER))
/*
* Convenience makro for getting the object header of a structure
*/
#define OBJ_HDR(obj) ((ObjectHeader *)(obj))
/*
* Convenience makro for getting the reference count of an object
*/
#define OBJ_HDR_RC(obj) (OBJ_HDR((obj))->referenceCount)
/*
* Convenience makro for getting the mutex for locking the reference count of an object
*/
#define OBJ_HDR_MUTEX_REF(obj) (&(OBJ_HDR((obj))->retainCountMutex))
/*
* globalMutex is used for locking by the fallback methods __sync_fetch_and_add_4 and __sync_sub_and_fetch_4.
*/
static pthread_mutex_t globalMutex = PTHREAD_MUTEX_INITIALIZER;
int objectHeaderInitialize(ObjectHeader *header, DeallocCallback callback)
{
header->referenceCount = 1;
header->callback = callback;
#if SUPPORT_FOR_ATOMIC == 0
if (pthread_mutex_init(&(header->retainCountMutex), NULL) != 0)
return -1;
#endif
return 0;
}
void *retain(void *object)
{
#if SUPPORT_FOR_ATOMIC
__sync_fetch_and_add(&OBJ_HDR_RC(object), 1);
#else
pthread_mutex_lock(OBJ_HDR_MUTEX_REF(object));
OBJ_HDR_RC(object)++;
pthread_mutex_unlock(OBJ_HDR_MUTEX_REF(object));
#endif
return object;
}
void release(void *object)
{
#if SUPPORT_FOR_ATOMIC
if (__sync_sub_and_fetch(&OBJ_HDR_RC(object), 1) <= 0)
OBJ_HDR(object)->callback(object);
#else
pthread_mutex_lock(OBJ_HDR_MUTEX_REF(object));
OBJ_HDR_RC(object)--;
pthread_mutex_unlock(OBJ_HDR_MUTEX_REF(object));
if (OBJ_HDR_RC(object) <= 0) {
pthread_mutex_destroy(OBJ_HDR_MUTEX_REF(object));
OBJ_HDR(object)->callback(object);
}
#endif
}
int retainCount(void *object)
{
return OBJ_HDR_RC(object);
}
/*
* Fallback for __sync_fetch_and_add. If gcc can't create code for the target processor, gcc will call this method instead.
*/
inline int __sync_fetch_and_add_4(int *ptr, int value)
{
pthread_mutex_lock(&globalMutex);
int tmp = *ptr;
*ptr += value;
pthread_mutex_unlock(&globalMutex);
return tmp;
}
/*
* Fallback for __sync_sub_and_fetch. If gcc can't create code for the target processor, gcc will call this method instead.
*/
inline int __sync_sub_and_fetch_4(int *ptr, int value)
{
pthread_mutex_lock(&globalMutex);
*ptr -= value;
int tmp = *ptr;
pthread_mutex_unlock(&globalMutex);
return tmp;
}