Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Locking a Hash Map when Rehashing

I'm trying to create a hash map that is thread-safe. I have a vector of mutexes for each bucket in the hash map to support a reader-writer lock for each entry in the hash map.

However, when I want to rehash the hash map, I want to lock the whole map so that no reads/writes can occur during rehashing.

I think I need an additional mutex, however do my Gets/Puts methods also need to acquire this mutex? How do I block other threads from doing Reads/Writes only when rehashing is occurring and not block each other when only writing and reading is happening?

This is how my current hash table class looks:

template<typename K, typename T>
class HashTable {
    int num_buckets_;
    double threshold_ratio_;
    int num_elements_;
    vector<vector<pair<T, K>>> table_;
    vector<mutex> read_write_locks_;
    mutex mu_;

    int GetHash(const K& key) {
        return hash<K>{}(key) % num_buckets_;
    }

    void Rehash() {
        scoped_lock<mutex> lock(mu_);   // Lock the whole table?
        cout << "Threshold Value has been reached. Rehashing...\n";

        vector<vector<T>> new_table(2 * num_buckets_);
        num_buckets_ = 2 * num_buckets_;

        vector<mutex> new_mutexes(2 * num_buckets_);
        read_write_locks_.swap(new_mutexes);
        // TODO : Implementation
    }

public:
    explicit HashTable(int num_buckets) : num_buckets_(num_buckets), threshold_ratio_(0.75), num_elements_(0) {
        table_.resize(num_buckets);
        vector<mutex> temp(num_buckets);
        read_write_locks_.swap(temp);
    }

    void Put(const K& key, const T& val) {
        ++num_elements_;
        if (static_cast<double>(num_elements_) / num_buckets_ > threshold_ratio_) {
            Rehash();
        }
        int hash_val = GetHash(key);
        scoped_lock<mutex> write_lock(read_write_locks_[hash_val]);
        cout << "Putting Key: " << key << ", Hash: "<< hash_val << "  with Value: " << val << '\n';
        table_[hash_val].push_back({val, key}); //TODO: For existing keys it should replace the value, not create a new entry
    }

    T Get(const K& key) {
        int hash_val = GetHash(key);
        scoped_lock<mutex> read_lock(read_write_locks_[hash_val]);

        if (table_[hash_val].size() >= 1) {
            for (const auto& elem : table_[hash_val]) {
                if (elem.second == key) {
                    cout << "Key: " << key << " gets value: " << elem.first << '\n';
                    return elem.first;
                }
            }
        }
        cerr << "Unable to find key in hash table. Terminating Program. \n";
        exit(EXIT_FAILURE);
    }
};
like image 806
Kris Avatar asked Nov 18 '25 02:11

Kris


1 Answers

This is overthinking. A single mutex protecting the entire container should be sufficient. But if you insist on implementing this kind of a complicated design:

  1. Use a std::shared_mutex for your global container mutex.
  2. The individual getters/putters will need to acquire a shared lock on the global mutex, before locking their individual hash buckets.
  3. Rehash acquires an exclusive lock, which blocks all getters/putters.
like image 179
Sam Varshavchik Avatar answered Nov 19 '25 17:11

Sam Varshavchik