1. 程式人生 > 實用技巧 >在GoLang中實現執行緒安全的字典

在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並不是執行緒安全的。