golang 讀寫鎖的那點事

RWMutex簡介

讀寫鎖實際是一種特殊的自旋鎖,它把對共享資源的訪問者劃分成讀者和寫者,讀者只對共享資源進行讀訪問,寫者則需要對共享資源進行寫操作。這種鎖相對於自旋鎖而言,能提高併發性,因爲在多處理器系統中,它允許同時有多個讀者來訪問共享資源,最大可能的讀者數爲實際的邏輯CPU數。寫者是排他性的,一個讀寫鎖同時只能有一個寫者或多個讀者(與CPU數相關),但不能同時既有讀者又有寫者。

應用場景

讀寫鎖多用於讀多寫少的場景

  1. 比如在配置中,讀取一次配置後,很少修改配置;但存在修改配置的情況。
  2. 在一些初始化的結構體,或者數組中,很少動用修改,大多數都是讀取操作。

問題

這裏有一個前提條件:在大併發讀的時候纔會發生
問:如果有10000 reader, 一個writer,writer要等多久才能實際發生write操作?
答: 寫,需要等到沒有讀鎖才能操作。
測試代碼如下:

package main

import (
	"sync"
	"fmt"
	"time"
)

func main() {
	rwMux := new(sync.RWMutex)
	names := make(map[int]string)
	for i := 0; i < 10; i++ {
		names[i] = fmt.Sprintf("name %d",i)
	}

	go func() {
		for i := 0; i < 100000; i++ {
			go func(i int) {
				rwMux.RLock()
				fmt.Println(names[1],i)
				rwMux.RUnlock()
			}(i)
		}
	}()

	time.Sleep(time.Millisecond)
	go func() {
		for i := 0; i < 10; i++ {
			go func(i int) {
				rwMux.Lock()
				names[i] = fmt.Sprintf("hello %d",i)
				fmt.Println("writer....")
				rwMux.Unlock()
			}(i)
		}
	}()
	time.Sleep(time.Second * 10)
}

所以說:寫的延遲完全沒有保障。

解決方案

方案當然不是我這菜鳥想的,是羣裏一個大佬隨便提了一句。
原話:

rwlock這種我覺得還不如用lock,然後定時copy出來一份給read,這樣他的資源 競爭被bound在thread個數上,好得多
副本的訪問更新用atomic操作讀寫就行了,這樣競爭被bound在cpu core個數上
atomic.LoadPointer來做object的更新

在此我的理解的是:

在寫的時候加互斥鎖,並返回一個副本,在讀的時候不加鎖,都去讀取整個副本。

後面又去查了一些資料,有興趣的可以去了解一下。
官方已經有大佬的思路實現
我這裏也貼出代碼:

package main

import (
	"sync"
	"sync/atomic"
)

func main() {
	type Map map[string]string
	var m atomic.Value
	m.Store(make(Map))
	var mu sync.Mutex // used only by writers
	// read function can be used to read the data without further synchronization
	read := func(key string) (val string) {
		m1 := m.Load().(Map)
		return m1[key]
	}
	// insert function can be used to update the data without further synchronization
	insert := func(key, val string) {
		mu.Lock() // synchronize with other potential writers
		defer mu.Unlock()
		m1 := m.Load().(Map) // load current value of the data structure
		m2 := make(Map)      // create a new value
		for k, v := range m1 {
			m2[k] = v // copy all data from the current object to the new one
		}
		m2[key] = val // do the update that we need
		m.Store(m2)   // atomically replace the current object with the new one
		// At this point all new readers start working with the new version.
		// The old version will be garbage collected once the existing readers
		// (if any) are done with it.
	}
	_, _ = read, insert
}

這就是官方的實現,思路都是一樣的。

寫在最後:

我寫這個文章主要是爲了說明,用一個技術的時候,不要僅僅停留在用的層面;同樣是大量讀,少量寫,爲什麼大併發來讀會有這些問題,你用的時候是否已經想到;所以多瞭解一下,多想一下爲什麼。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章