Golang program to implement a concurrent hash map


Concurrent hash maps can be perceived as efficient data structures for ensuring smooth and parallel execution. Designed to handle concurrent read and write operations efficiently, it is a valuable tool for building highly performant multi-threaded applications. In this article, we learn to implement a concurrent hash map in go.

Syntax

func NewConcurrentMap(size int) *ConcurrentMap

The syntax defines a function named NewConcurrentMap defined to create and return an instance of a customized concurrent hash map named ConcurrentMap. Taking a size parameter to determine internal bucket count for data distribution, it encapsulates the initialization process.

Algorithm

  • Start by initializing a fixed-size array as an underlying storage for the hash map.

  • Implement a hash function for mapping keys to indices within the array.

  • For each index, we create separate linked lists to avoid collisions.

  • Define methods for addition, retrieval, and deleting key-value pairs while ensuring synchronization.

  • To enable goroutines to access parts of the hash map simultaneously, finally implement a locking mechanism such as mutexes.

Example 1

In this example to implement a concurrent hash map in go we use sync.Map() package and goroutines, we directly build a concurrent map. We then use the empty concurrent map to concurrently insert key-value pairs using goroutines but also simultaneously employing synchronisation to ensure data consistency. We then use concurrent goroutines to retrieve values associated with keys from the map to demonstrate thread-safe access.

package main
import (
    "fmt"
	"sync"
)
func main() {
	concurrentMap := &sync.Map{}
	fmt.Println("Step 1: Concurrent Map Created")
    fmt.Println()
    var wg sync.WaitGroup
    for i := 0; i < 5; i++ {
        wg.Add(1)
    	go func(index int) {
        	defer wg.Done()
        	key := fmt.Sprintf("key%d", index)
        	value := fmt.Sprintf("value%d", index)
        	concurrentMap.Store(key, value)
            fmt.Printf("Step 2: Inserted Key: %s, Value: %s\n", key, value)
    	}(i)
    }
	wg.Wait()
    fmt.Println()
    for i := 0; i < 5; i++ {
        wg.Add(1)
    	go func(index int) {
        	defer wg.Done()
        	key := fmt.Sprintf("key%d", index)
            if value, exists := concurrentMap.Load(key); exists {
            	fmt.Printf("Step 3: Retrieved Key: %s, Value: %s\n", key, value)
        	}
        }(i)
    }
    wg.Wait()
}

Output

Step 1: Concurrent Map Created

Step 2: Inserted Key: key4, Value: value4
Step 2: Inserted Key: key0, Value: value0
Step 2: Inserted Key: key1, Value: value1
Step 2: Inserted Key: key2, Value: value2
Step 2: Inserted Key: key3, Value: value3

Step 3: Retrieved Key: key4, Value: value4
Step 3: Retrieved Key: key0, Value: value0
Step 3: Retrieved Key: key1, Value: value1
Step 3: Retrieved Key: key2, Value: value2
Step 3: Retrieved Key: key3, Value: value3

Example 2

In this example, we newly introduce a custom concurrent hash map implementation namely – ConcurrentMap type with individual mutexes for each bucket to maintain synchronization. The defined NewConcurrentMap function initialized the map with a given number of buckets and corresponding mutexes. Then Insert and Get methods are used with the calculated bucket index to control access to the map, lock and unlock the associated mutex ensuring thread-safe insertion and retrieval.

package main
import (
        	"fmt"
        	"sync"
)
type ConcurrentMap struct {
	buckets []map[string]string
	mutexes []sync.Mutex
}
func NewConcurrentMap(size int) *ConcurrentMap {
	cm := &ConcurrentMap{make([]map[string]string, size), make([]sync.Mutex, size)}
	for i := range cm.buckets {
      	cm.buckets[i] = make(map[string]string)
    }
	fmt.Println("Step 1: Custom Concurrent Map Created")
    return cm
}
func (cm *ConcurrentMap) Insert(key, value string) {
	index := hash(key) % len(cm.buckets)
    cm.mutexes[index].Lock()
	defer cm.mutexes[index].Unlock()
	cm.buckets[index][key] = value
    fmt.Printf("Step 2: Inserted Key: %s, Value: %s\n", key, value)
}
func (cm *ConcurrentMap) Get(key string) (string, bool) {
	index := hash(key) % len(cm.buckets)
	cm.mutexes[index].Lock()
	defer cm.mutexes[index].Unlock()
	val, exists := cm.buckets[index][key]
	return val, exists
}
func hash(key string) int {
	return len(key)
}
func main() {
	concurrentMap := NewConcurrentMap(10)
	fmt.Println()
	var wg sync.WaitGroup
	for i := 0; i < 5; i++ {
      	wg.Add(1)
    	go func(index int) { defer wg.Done(); key, value := fmt.Sprintf("key%d", index), fmt.Sprintf("value%d", index); concurrentMap.Insert(key, value) }(i)
	}
    wg.Wait()
    fmt.Println()
	for i := 0; i < 5; i++ {
        wg.Add(1)
        go func(index int) { defer wg.Done(); key := fmt.Sprintf("key%d", index); if value, exists := concurrentMap.Get(key); exists { fmt.Printf("Step 3: Retrieved Key: %s, Value: %s\n", key, value) } }(i)
    }
    wg.Wait()
}

Output

Step 1: Custom Concurrent Map Created

Step 2: Inserted Key: key4, Value: value4
Step 2: Inserted Key: key0, Value: value0
Step 2: Inserted Key: key1, Value: value1
Step 2: Inserted Key: key2, Value: value2
Step 2: Inserted Key: key3, Value: value3

Step 3: Retrieved Key: key4, Value: value4
Step 3: Retrieved Key: key0, Value: value0
Step 3: Retrieved Key: key1, Value: value1
Step 3: Retrieved Key: key2, Value: value2
Step 3: Retrieved Key: key3, Value: value3

Real Life Implementation

  • Plagiarism Detection: Hash maps are used in plagiarism detection systems to store the hash values of phrases or text blocks taken from documents. This feature allows for the easy comparison and identification of identical or indistinguishable data, assisting in the detection of suspected instances of plagiarism.

  • The Social Media Follower System: Consider a comparable social media network like Instagram. The platform uses hash maps to manage the followers and followers linked with each user. Clicking on the "Follow" button on a user's profile, immediately updates a hash map, creating a connection between the unique user ID and the user ID of the person chosen to follow. This allows easy retrieval of one’s followers as well as those who are followed by the person, boosting feed personalization.

Conclusion

A concurrent hash map is a crucial data structure in modern parallel programming, allowing multiple threads to access and modify data concurrently while maintaining synchronization. In this article, we have elaborated the concept to implement a concurrent hash map in go through 2 different methods. The first approach using the built-in sync.Map type dynamically handles synchronization, and is efficient for scenarios involving multiple threads or goroutines. The next custom implementation approach offers fine-grained control over synchronization, allowing explicit management of concurrency and offering insights into the synchronization process.

Updated on: 18-Oct-2023

102 Views

Kickstart Your Career

Get certified by completing the course

Get Started
Advertisements