diff --git a/hashmap/myhashmap.c b/hashmap/myhashmap.c index 520bed1..b71145d 100644 --- a/hashmap/myhashmap.c +++ b/hashmap/myhashmap.c @@ -5,23 +5,28 @@ #include /* - * @brief Returns the mutex ID. + * @brief Returns the mutex ID based on hash value. */ -static size_t get_mutex(hashmap_s *hashmap, size_t hash) { +static inline size_t get_mutex(hashmap_s *hashmap, size_t hash) { return hash % hashmap->num_locks; } -static size_t get_bucket_index(hashmap_s *hashmap, void *key) { +/* + * @brief Returns the bucket index for a given key. + */ +static inline size_t get_bucket_index(hashmap_s *hashmap, void *key) { unsigned int hash = hashmap->hash(key); return (size_t)(hash % MYCLIB_HASHMAP_SIZE); } +/* + * @brief Free the contents of a bucket (key and value). + */ static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) { if (bucket == NULL) { return; } - /* Free key if free function is provided, otherwise free raw pointer if allocated */ if (bucket->key != NULL) { if (hashmap->free_key != NULL) { hashmap->free_key(bucket->key); @@ -31,7 +36,6 @@ static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) { bucket->key = NULL; } - /* Free value if free function is provided, otherwise free raw pointer if allocated */ if (bucket->value != NULL) { if (hashmap->free_value != NULL) { hashmap->free_value(bucket->value); @@ -42,17 +46,19 @@ static void free_bucket_content(hashmap_s *hashmap, bucket_s *bucket) { } } +/* + * @brief Find a bucket by key in the chain. + * @param[out] prev Set to the previous bucket in the chain (or NULL if first). + */ static bucket_s *find_bucket(hashmap_s *hashmap, void *key, bucket_s **prev) { size_t index = get_bucket_index(hashmap, key); bucket_s *bucket = &hashmap->map[index]; *prev = NULL; - /* If primary bucket has no key, nothing stored here */ if (bucket->key == NULL) { return NULL; } - /* Search through chain */ while (bucket != NULL) { if (hashmap->equal(bucket->key, key)) { return bucket; @@ -75,8 +81,6 @@ hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn, return NULL; } - memset(hashmap, 0, sizeof *hashmap); - hashmap->hash = hash_fn; hashmap->equal = equal_fn; hashmap->free_key = free_key_fn; @@ -84,7 +88,7 @@ hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn, hashmap->key_size = key_size; hashmap->value_size = value_size; - hashmap->size = 0; + atomic_init(&hashmap->size, 0); hashmap->num_locks = 64; hashmap->locks = malloc(sizeof(mtx_t) * hashmap->num_locks); @@ -93,22 +97,17 @@ hashmap_s *hm_new(hash_f *hash_fn, equal_f *equal_fn, free_key_f *free_key_fn, return NULL; } - int ret; for (size_t i = 0; i < hashmap->num_locks; ++i) { - ret = mtx_init(&(hashmap->locks[i]), mtx_recursive); - if (ret != thrd_success) { - /* Mutex failed: cleanup previous inits and return NULL */ + if (mtx_init(&(hashmap->locks[i]), mtx_plain) != thrd_success) { for (size_t j = 0; j < i; ++j) { mtx_destroy(&(hashmap->locks[j])); } - free(hashmap->locks); free(hashmap); return NULL; } } - /* Initialize buckets to zero */ memset(hashmap->map, 0, sizeof(hashmap->map)); return hashmap; @@ -119,7 +118,6 @@ void hm_free(hashmap_s *hashmap) { return; } - /* Iterate through all buckets in the hash map */ for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { bucket_s *bucket = &hashmap->map[i]; @@ -127,35 +125,20 @@ void hm_free(hashmap_s *hashmap) { free_bucket_content(hashmap, bucket); } - /* Free all chained buckets */ bucket = bucket->next; while (bucket != NULL) { bucket_s *next = bucket->next; - if (bucket->key != NULL || bucket->value != NULL) { - if (hashmap->free_key != NULL && bucket->key != NULL) { - hashmap->free_key(bucket->key); - } else if (bucket->key != NULL) { - free(bucket->key); - } - - if (hashmap->free_value != NULL && bucket->value != NULL) { - hashmap->free_value(bucket->value); - } else if (bucket->value != NULL) { - free(bucket->value); - } - } + free_bucket_content(hashmap, bucket); free(bucket); bucket = next; } } - /* Free the mutexes */ for (size_t i = 0; i < hashmap->num_locks; ++i) { mtx_destroy(&(hashmap->locks[i])); } free(hashmap->locks); - /* Free the hash map structure */ free(hashmap); } @@ -174,8 +157,10 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { return false; } - size_t mutex_id = get_mutex(hashmap, (size_t)hashmap->hash(key)); + unsigned int hash = hashmap->hash(key); + size_t mutex_id = get_mutex(hashmap, hash); mtx_t *mutex = &(hashmap->locks[mutex_id]); + if (mtx_lock(mutex) != thrd_success) { return false; } @@ -184,30 +169,32 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { bucket_s *existing = find_bucket(hashmap, key, &prev); if (existing != NULL) { + /* Key exists - update value */ + void *new_value = malloc(hashmap->value_size); + if (new_value == NULL) { + mtx_unlock(mutex); + return false; + } + memcpy(new_value, value, hashmap->value_size); + + /* Free old value and assign new one */ if (hashmap->free_value != NULL && existing->value != NULL) { hashmap->free_value(existing->value); } else if (existing->value != NULL) { free(existing->value); } - - existing->value = malloc(hashmap->value_size); - if (existing->value == NULL) { - mtx_unlock(mutex); - return false; - } - - memcpy(existing->value, value, hashmap->value_size); + existing->value = new_value; mtx_unlock(mutex); return true; } - /* Key doesn't exist, need to insert new bucket */ + /* Key doesn't exist - insert new bucket */ size_t index = get_bucket_index(hashmap, key); bucket_s *bucket = &hashmap->map[index]; if (bucket->key == NULL) { - /* First bucket is empty, use it */ + /* Primary bucket is empty */ bucket->key = malloc(hashmap->key_size); if (bucket->key == NULL) { mtx_unlock(mutex); @@ -226,20 +213,17 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { memcpy(bucket->value, value, hashmap->value_size); bucket->next = NULL; - hashmap->size++; + atomic_fetch_add(&hashmap->size, 1); mtx_unlock(mutex); return true; } - /* Collision: create new bucket and insert at head (after primary) */ + /* Collision - create new bucket and chain it */ bucket_s *new_bucket = malloc(sizeof(bucket_s)); if (new_bucket == NULL) { mtx_unlock(mutex); return false; } - new_bucket->key = NULL; - new_bucket->value = NULL; - new_bucket->next = NULL; new_bucket->key = malloc(hashmap->key_size); if (new_bucket->key == NULL) { @@ -259,15 +243,18 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value) { memcpy(new_bucket->key, key, hashmap->key_size); memcpy(new_bucket->value, value, hashmap->value_size); - /* Insert new_bucket at head of chain */ + /* Insert at head of chain */ new_bucket->next = bucket->next; bucket->next = new_bucket; - hashmap->size++; + atomic_fetch_add(&hashmap->size, 1); mtx_unlock(mutex); return true; } +/* + * @brief Create a copy of a bucket. + */ static bucket_s *get_bucket_copy(bucket_s *from, size_t key_size, size_t value_size) { if (from == NULL) { return NULL; @@ -300,8 +287,6 @@ static bucket_s *get_bucket_copy(bucket_s *from, size_t key_size, size_t value_s memcpy(copy->value, from->value, value_size); } - copy->next = NULL; - return copy; } @@ -310,8 +295,10 @@ bucket_s *hm_get(hashmap_s *hashmap, void *key) { return NULL; } - size_t mutex_id = get_mutex(hashmap, (size_t)hashmap->hash(key)); + unsigned int hash = hashmap->hash(key); + size_t mutex_id = get_mutex(hashmap, hash); mtx_t *mutex = &(hashmap->locks[mutex_id]); + if (mtx_lock(mutex) != thrd_success) { return NULL; } @@ -319,14 +306,13 @@ bucket_s *hm_get(hashmap_s *hashmap, void *key) { bucket_s *prev = NULL; bucket_s *found = find_bucket(hashmap, key, &prev); - if (found) { - bucket_s *copy = get_bucket_copy(found, hashmap->key_size, hashmap->value_size); - mtx_unlock(mutex); - return copy; + bucket_s *copy = NULL; + if (found != NULL) { + copy = get_bucket_copy(found, hashmap->key_size, hashmap->value_size); } mtx_unlock(mutex); - return NULL; + return copy; } bool hm_remove(hashmap_s *hashmap, void *key) { @@ -334,8 +320,10 @@ bool hm_remove(hashmap_s *hashmap, void *key) { return false; } - size_t mutex_id = get_mutex(hashmap, (size_t)hashmap->hash(key)); + unsigned int hash = hashmap->hash(key); + size_t mutex_id = get_mutex(hashmap, hash); mtx_t *mutex = &(hashmap->locks[mutex_id]); + if (mtx_lock(mutex) != thrd_success) { return false; } @@ -349,7 +337,9 @@ bool hm_remove(hashmap_s *hashmap, void *key) { } if (prev == NULL) { + /* Removing primary bucket */ if (to_remove->next != NULL) { + /* Move next bucket content to primary */ bucket_s *next_bucket = to_remove->next; free_bucket_content(hashmap, to_remove); @@ -360,19 +350,18 @@ bool hm_remove(hashmap_s *hashmap, void *key) { free(next_bucket); } else { + /* No chain, just clear */ free_bucket_content(hashmap, to_remove); to_remove->next = NULL; } } else { - free_bucket_content(hashmap, to_remove); - + /* Removing from chain */ prev->next = to_remove->next; + free_bucket_content(hashmap, to_remove); free(to_remove); } - if (hashmap->size > 0) { - hashmap->size--; - } + atomic_fetch_sub(&hashmap->size, 1); mtx_unlock(mutex); return true; @@ -383,17 +372,7 @@ size_t hm_size(hashmap_s *hashmap) { return 0; } - /* Use the first mutex to protect read of size */ - mtx_t *mutex = &hashmap->locks[0]; - if (mtx_lock(mutex) != thrd_success) { - return 0; - } - - size_t size = hashmap->size; - - mtx_unlock(mutex); - - return size; + return atomic_load(&hashmap->size); } bool hm_contains(hashmap_s *hashmap, void *key) { @@ -401,15 +380,13 @@ bool hm_contains(hashmap_s *hashmap, void *key) { return false; } - bool res = false; - bucket_s *b = hm_get(hashmap, key); if (b != NULL) { - res = true; hm_free_bucket(b); + return true; } - return res; + return false; } void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) { @@ -417,14 +394,14 @@ void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) { return; } - for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { - size_t mutex_id = get_mutex(hashmap, i); - mtx_t *mutex = &hashmap->locks[mutex_id]; - if (mtx_lock(mutex) != thrd_success) { - continue; - } + /* Lock all mutexes */ + for (size_t i = 0; i < hashmap->num_locks; ++i) { + mtx_lock(&hashmap->locks[i]); + } + for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { bucket_s *bucket = &hashmap->map[i]; + if (bucket->key != NULL) { bucket_s *copy = get_bucket_copy(bucket, hashmap->key_size, hashmap->value_size); if (copy != NULL) { @@ -442,8 +419,11 @@ void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)) { } bucket = bucket->next; } + } - mtx_unlock(mutex); + /* Unlock all mutexes */ + for (size_t i = 0; i < hashmap->num_locks; ++i) { + mtx_unlock(&hashmap->locks[i]); } } @@ -452,13 +432,12 @@ void hm_clear(hashmap_s *hashmap) { return; } - for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { - size_t mutex_id = get_mutex(hashmap, i); - mtx_t *mutex = &hashmap->locks[mutex_id]; - if (mtx_lock(mutex) != thrd_success) { - continue; - } + /* Lock all mutexes */ + for (size_t i = 0; i < hashmap->num_locks; ++i) { + mtx_lock(&hashmap->locks[i]); + } + for (size_t i = 0; i < MYCLIB_HASHMAP_SIZE; ++i) { bucket_s *bucket = &hashmap->map[i]; if (bucket->key != NULL || bucket->value != NULL) { @@ -468,20 +447,7 @@ void hm_clear(hashmap_s *hashmap) { bucket = bucket->next; while (bucket != NULL) { bucket_s *next = bucket->next; - if (bucket->key != NULL) { - if (hashmap->free_key != NULL) { - hashmap->free_key(bucket->key); - } else { - free(bucket->key); - } - } - if (bucket->value != NULL) { - if (hashmap->free_value != NULL) { - hashmap->free_value(bucket->value); - } else { - free(bucket->value); - } - } + free_bucket_content(hashmap, bucket); free(bucket); bucket = next; } @@ -489,9 +455,12 @@ void hm_clear(hashmap_s *hashmap) { hashmap->map[i].key = NULL; hashmap->map[i].value = NULL; hashmap->map[i].next = NULL; - - mtx_unlock(mutex); } - hashmap->size = 0; + atomic_store(&hashmap->size, 0); + + /* Unlock all mutexes */ + for (size_t i = 0; i < hashmap->num_locks; ++i) { + mtx_unlock(&hashmap->locks[i]); + } } diff --git a/hashmap/myhashmap.h b/hashmap/myhashmap.h index f5ea5ad..fd33eda 100644 --- a/hashmap/myhashmap.h +++ b/hashmap/myhashmap.h @@ -1,6 +1,7 @@ #ifndef MYCLIB_HASHMAP_H #define MYCLIB_HASHMAP_H +#include #include #include #include @@ -50,6 +51,7 @@ typedef void free_value_f(void *value); /** * @brief Main structure representing the hash map. + * Thread-safe for concurrent operations on different keys. */ typedef struct hashmap { hash_f *hash; /**< Hash function */ @@ -59,7 +61,7 @@ typedef struct hashmap { size_t key_size; /**< Size in bytes of the key */ size_t value_size; /**< Size in bytes of the value */ bucket_s map[MYCLIB_HASHMAP_SIZE]; /**< Array of bucket chains */ - size_t size; /* Hashmap size (number of keys) */ + atomic_size_t size; /**< Hashmap size (number of keys) - atomic */ mtx_t *locks; /**< Mutex array */ size_t num_locks; /**< Number of mutex */ } hashmap_s; @@ -113,7 +115,7 @@ bool hm_set(hashmap_s *hashmap, void *key, void *value); * @param[in] hashmap Pointer to the hash map. * @param[in] key Pointer to the key to search for. * @return Pointer to the copy of the bucket or NULL on failure. - * @note Free after use. + * @note Free after use with hm_free_bucket(). */ bucket_s *hm_get(hashmap_s *hashmap, void *key); @@ -129,9 +131,36 @@ bucket_s *hm_get(hashmap_s *hashmap, void *key); */ bool hm_remove(hashmap_s *hashmap, void *key); +/** + * @brief Get the number of entries in the hash map. + * + * @param[in] hashmap Pointer to the hash map. + * @return Number of key-value pairs in the map. + */ size_t hm_size(hashmap_s *hashmap); + +/** + * @brief Check if a key exists in the hash map. + * + * @param[in] hashmap Pointer to the hash map. + * @param[in] key Pointer to the key to search for. + * @return true if the key exists, false otherwise. + */ bool hm_contains(hashmap_s *hashmap, void *key); + +/** + * @brief Iterate over all entries in the hash map. + * + * @param[in] hashmap Pointer to the hash map. + * @param[in] callback Function called for each bucket. + */ void hm_foreach(hashmap_s *hashmap, void (*callback)(bucket_s *bucket)); + +/** + * @brief Remove all entries from the hash map. + * + * @param[in] hashmap Pointer to the hash map. + */ void hm_clear(hashmap_s *hashmap); #endif /* MYCLIB_HASHMAP_H */