基盤を構築するための財団
ほとんどの言語では、元のマップがあれば、あなたが大規模なロックを追加することに加えて、複数のスレッドや必要性についてのスレッドゴルーチンロックへの変更、異なる言語だけでなく、さまざまな最適化をしたいという、スレッドセーフなデータ構造ではありません方法は、Javaの法律のリンクリストのように、実際にマップを実装するために使用される、この言語で行く、この論文では、シーンを解析し、
セキュリティマップを達成するために、同時三つの方法
外出先言語でマップを変更するには複数の同時セキュアなアクセスを実現するためのゴルーチンの方法は、以下の3つがあります。
実装 | 原則 | 該当シーン |
---|---|---|
+ミューテックスをマッピング | ミューテックスミューテックスを経由してマップに複数のゴルーチンシリアライズ・アクセスを実現するために、 | 読み書きするミューテックスロックによって必要とシーンに読み書き近い比のためにロックを解除しています |
+ RWMutexをマッピング | RWMutexマップによって達成読み取りおよび書き込み読み取りおよび書き込みロックは、それによって、読み出し性能の並行性を向上させる、分離ロック | シーンを書くためにどのくらいの読み取りに適用ミューテックスと比較すると |
sync.Map | アトミック命令マップを読んプライを分離し、何のロックは何のロックを読まないことを保証するために読んでいないと遅延された様式により更新さ約達成するために | 読む少し修正する以上、削除要素は、周波数は、ほとんどの場合、代わりに2つの実装で、高くない増加します |
3種類以上の多くの研究がそうするとき、パフォーマンスのボトルネック、その実装の詳細のソースを理解することである、したがって、そのようなテストデータ統合の量など、さまざまなビジネス・シナリオやプラットフォーム、に対して特異的であることがあり、特定のパフォーマンスの違いを達成するために解決するために解決策を見つけるために分析することができます
容量の問題のマップ
時間と地図マップミューテックスとRWMutex実装におけるセキュリティの同時欠失の要素数の増加に伴って、容量がある場合には、しばしばいくつかの点で、そのようなデータ増加の大きい数として、増加していき、その後大削除の数とそれらのマップ及び要素は地図を読むために上昇した場合にかなり狭い要素を除去する能力で終わり、そしてsync.Mapでなくなり、容量を縮小することができる、汚いから再構築されます
別々の読み取りと書き込みロックしないと読むん
別々の読み取りと書き込み
同時アクセスマップ読書の主な問題は、実際には時間展開は、他の要素につながる可能性があり、私は読んでいない場合は同期しながら、拡張操作のためのマップは、同時に安全なアクセスを実行することができることを、ハッシュアドレスです。その内部マップが汚れによって保存される要素を追加するこの方法を採用しています
いいえロック読み取りません
通过read只读和dirty写map将操作分离,其实就只需要通过原子指令对read map来进行读操作而不需要加锁了,从而提高读的性能
写加锁与延迟提升
写加锁
上面提到增加元素操作可能会先增加大dirty写map中,那针对多个goroutine同时写,其实就需要进行Mutex加锁了
延迟提升
上面提到了read只读map和dirty写map, 那就会有个问题,默认增加元素都放在dirty中,那后续访问新的元素如果都通过 mutex加锁,那read只读map就失去意义,sync.Map中采用一直延迟提升的策略,进行批量将当前map中的所有元素都提升到read只读map中从而为后续的读访问提供无锁支持
指针与惰性删除
map里面的指针
map里面存储数据都会涉及到一个问题就是存储值还是指针,存储值可以让 map作为一个大的的对象,减轻垃圾回收的压力(避免扫描所有小对象),而存储指针可以减少内存利用,而sync.Map中其实采用了指针结合惰性删除的方式,来进行 map的value的存储
惰性删除
惰性删除是并发设计中一中常见的设计,比如删除某个个链表元素,如果要删除则需要修改前后元素的指针,而采用惰性删除,则通常只需要给某个标志位设定为删除,然后在后续修改中再进行操作,sync.Map中也采用这种方式,通过给指针指向某个标识删除的指针,从而实现惰性删除
源码分析
数据结构分析
Map
type Map struct {
mu Mutex
// read是一个readOnly的指针,里面包含了一个map结构,就是我们说的只读map对该map的元素的访问
// 不需要加锁,只需要通过atomic加载最新的指针即可
read atomic.Value // readOnly
// dirty包含部分map的键值对,如果要访问需要进行mutex获取
// 最终dirty中的元素会被全部提升到read里面的map中
dirty map[interface{}]*entry
// misses是一个计数器用于记录从read中没有加载到数据
// 尝试从dirty中进行获取的次数,从而决定将数据从dirty迁移到read的时机
misses int
}
readOnly
只读map,对该map元素的访问不需要加锁,但是该map也不会进行元素的增加,元素会被先添加到dirty中然后后续再转移到read只读map中,通过atomic原子操作不需要进行锁操作
type readOnly struct {
// m包含所有只读数据,不会进行任何的数据增加和删除操作
// 但是可以修改entry的指针因为这个不会导致map的元素移动
m map[interface{}]*entry
// 标志位,如果为true则表明当前read只读map的数据不完整,dirty map中包含部分数据
amended bool
}
entry
entry是sync.Map中值得指针,如果当p指针指向expunged这个指针的时候,则表明该元素被删除,但不会立即从map中删除,如果在未删除之前又重新赋值则会重用该元素
type entry struct {
// 指向元素实际值得指针
p unsafe.Pointer // *interface{}
}
数据的存储
2.2.1 元素存在只读map
元素如果存储在只读map中,则只需要获取entry元素,然后修改其p的指针指向新的元素就可以了,因为是原地操作所以map不会发生变化
read, _ := m.read.Load().(readOnly)
if e, ok := read.m[key]; ok && e.tryStore(&value) {
return
}
元素存在进行变更后的只读map中
如果此时发现元素存在只读 map中,则证明之前有操作触发了从dirty到read map的迁移,如果此时发现存在则修改指针即可
read, _ = m.read.Load().(readOnly)
if e, ok := read.m[key]; ok {
if e.unexpungeLocked() {
// The entry was previously expunged, which implies that there is a
// non-nil dirty map and this entry is not in it.
// 如果key之前已经被删除,则这个地方会将key从进行nil覆盖之前已经删除的指针
// 然后将它加入到dirty中
m.dirty[key] = e
}
// 调用atomic进行value存储
e.storeLocked(&value)
}
元素存在dirty map中
如果元素存在dirty中其实同read map逻辑一样,只需要修改对应元素的指针即可
} else if e, ok := m.dirty[key]; ok {
// 如果已经在dirty中就会直接存储
e.storeLocked(&value)
} else {
元素之前不存在
如果元素之前不存在当前Map中则需要先将其存储在dirty map中,同时将amended标识为true,即当前read中的数据不全,有一部分数据存储在dirty中
// 如果当前不是在修正状态
if !read.amended {
// 新加入的key会先被添加到dirty map中, 并进行read标记为不完整
// 如果dirty为空则将read中的所有没有被删除的数据都迁移到dirty中
m.dirtyLocked()
m.read.Store(readOnly{m: read.m, amended: true})
}
m.dirty[key] = newEntry(value)
数据迁移
read map到dirty map的迁移
在刚初始化和将所有元素迁移到read中后,dirty默认都是nil元素,而此时如果有新的元素增加,则需要先将read map中的所有未删除数据先迁移到dirty中
func (m *Map) dirtyLocked() {
if m.dirty != nil {
return
}
read, _ := m.read.Load().(readOnly)
m.dirty = make(map[interface{}]*entry, len(read.m))
for k, e := range read.m {
if !e.tryExpungeLocked() {
m.dirty[k] = e
}
}
}
dirty到read map的迁移
当持续的从read访问穿透到dirty中后,就会触发一次从dirty到read的迁移,这也意味着如果我们的元素读写比差比较小,其实就会导致频繁的迁移操作,性能其实可能并不如rwmutex等实现
func (m *Map) missLocked() {
m.misses++
if m.misses < len(m.dirty) {
return
}
m.read.Store(readOnly{m: m.dirty})
m.dirty = nil
m.misses = 0
}
数据加载
只读无锁
Load数据的时候回先从read中获取,如果此时发现元素,则直接返回即可
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
加锁读取read和dirty
加锁后会尝试从read和dirty中读取,同时进行misses计数器的递增,如果满足迁移条件则会进行数据迁移
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
e, ok = m.dirty[key]
// 这里将采取缓慢迁移的策略
// 只有当misses计数==len(m.dirty)的时候,才会将dirty里面的数据全部晋升到read中
m.missLocked()
}
数据删除
数据删除则分为两个过程,如果数据在read中,则就直接修改entry的标志位指向删除的指针即可,如果当前read中数据不全,则需要进行dirty里面的元素删除尝试,如果存在就直接从dirty中删除即可
func (m *Map) Delete(key interface{}) {
read, _ := m.read.Load().(readOnly)
e, ok := read.m[key]
if !ok && read.amended {
m.mu.Lock()
read, _ = m.read.Load().(readOnly)
e, ok = read.m[key]
if !ok && read.amended {
delete(m.dirty, key)
}
m.mu.Unlock()
}
if ok {
e.delete()
}
}
mutex与加锁后的read map重复读
ミューテックスミューテックスはm.lock時間の間に、ダーティリード動作にリフトが進行しているしている、実行が完了した場合、削除ダーティマップの変更、データ移行、など、すべての操作は、実際に汚れていないということですのでデータは、それが今回の繰り返しが再び読んで読み取ることがあります
マイクロ・シグナル:baxiaoshi2020
より多くの焦点SFASソースコード解析の記事を読みます
より多くの記事の懸念www.sreguide.com
ブログ記事複数のプラットフォームによる記事OpenWriteリリース!