在GoLang中实现线程安全的字典

1. 背景

本文主要解释如何通过RWMutex来实现一个基于内存的字典数据结构。

在项目中,经常需要与并发打交道,这其中很难避免会遇到多个并发的用户同时获取内存中数据的情况,因此我们必须能够有一种方式,可以对这些数据进行读写并发控制。

2. 实现

2.1 数据结构定义

为了达到我们的需求,我设计了以下的自定义的数据结构

package dictionary

import "sync"

type iKey interface{}
type iValue interface{}

type Dictionary struct {
    items map[iKey]iValue
    lock sync.RWMutex
}

对于上面的结构作如下说明:

  • items用于保存所有的key-value数据
  • lock用于控制用户对items的读写操作

对于RWMutex作如下说明:
Lock():每次只允许有一个goroutine在同一时刻获取读写
RLock(): 同一时刻可以有多个goroutine获取

2.2 方法实现

接下来,我们实现一下这个Dictionary中所支持一些操作。

2.2.1 添加key-value

// Add 向dictionary中添加一个新的key/value
func (d *Dictionary) Add(key iKey, value iValue) {
	d.lock.Lock()
	defer d.lock.Unlock()
	
	if d.items == nil {
		d.items = make(map[iKey]iValue)
	}
	
	d.items[key] = value
}

说明:

  • 在向dictionary中添加新的key/value前,先通过d.lock.Lock()获取到一个锁,这样可以有效避免一些误操作。当插入成功后,通过d.lock.Unlock()把刚才获取到锁释放掉。
  • 如果一个请求已经获取到了Lock()锁,那么另外一个想要获取RLock()的请求将不得不等待第一个请求释放锁(Unlock())

2.2.2 删除key-value

func (d *Dictionary) Remove(key iKey) bool{
	d.lock.Lock()
	defer d.lock.Unlock()
	
	if _, ok := d.items[key]; ok {
		delete(d.items, key)
	}
	
	return true
}

思路同Add这里不再多讲。
删除操作可以看成是一个操作,因此,同样需要使用Lock()Unlock()

2.2.3 获取key-value

func (d *Dictionary) Get(key iKey) iValue {
	d.lock.RLock()
	defer d.lock.RUnlock()
	
	return d.items[key]
}

需要强调一点是,如果仅仅是在多个goroutine中并发的去读取数据,那么将不会存在数据竞争的问题。如果我们需要获取Lock()锁,那么必须等到执行完RUnlock()才可以。

2.2.4 key是否存在

func (d *Dictionary) Exist(key iKey) bool {
	d.lock.RLock()
	defer d.lock.RUnlock()
	
	if _, ok := d.items[key]; ok {
		return true
	} else {
		return false
	}
}

这个操作我们认为他是一个操作。因此这里使用的是RLock()RUnlock()

2.2.5 清空所有key-value

func (d *Dictionary) Clear() {
	d.lock.Lock()
	defer d.lock.Unlock()
	d.items = make(map[iKey]iValue)
}

这个操作需要获取Lock()Unlock()

2.2.6 获取字典中元素的个数

func (d *Dictionary) Size() int {
	d.lock.RLock()
	defer d.lock.RUnlock()
	
	return len(d.items)
}

需要先获得读锁

2.2.7 获取字典中所有的key

func (d *Dictionary) GetKeys() []iKey {
	d.lock.RLock()
	defer d.lock.RUnlock()
	
	tempKeys := make([]iKey, 0)
	for i := range d.items {
		tempKeys = append(tempKeys, i)
	}
	
	return tempKeys
}

2.2.8 获取字典中所有的value

func (d *Dictionary) GetValues() []iValue {
	d.lock.RLock()
	defer d.lock.RUnlock()
	
	tempValues := make([]iValue, 0)
	for _, v := range d.items {
		tempValues = append(tempValues, v)
	}
	
	return tempValues
}

3. 小结

GoLang通过内置变量map和包sync.RWMutex提供了一个非常方便的实现字典的方法。map并不是线程安全的。

posted @ 2020-09-03 16:59  大海星  阅读(347)  评论(0编辑  收藏  举报