Goに付属する辞書タイプのマップは、同時に安全ではありません。
Go 1.9では、同時に安全な辞書タイプのsync.Mapが正式に追加されました。
このディクショナリタイプは、一般的に使用されるいくつかのキー値アクセス操作メソッドを提供し、これらの操作の同時セキュリティを保証します。同時に、保存、フェッチ、削除などの操作は、基本的に一定の時間内に完了することが保証されています。言い換えると、それらのアルゴリズムの複雑さはマップタイプと同様にO(1)です。
場合によっては、sync.Mapを使用すると、純粋にネイティブのマップおよびミューテックスのロックスキームと比較して、ロックの競合を大幅に減らすことができます。sync.Map自体もロックを使用しますが、実際にはロックをできるだけ使用しないようにします。
ロックの使用は、いくつかの並行操作を強制的にシリアル化する必要があることを意味します。これにより、特にコンピューターに複数のCPUコアがある場合、プログラムのパフォーマンスが低下することがよくあります。したがって、ロックを使用せずにアトミック操作を使用できるとよく言われますが、これは非常に制限されています。
sync.Mapをどのようなシナリオで使用する場合でも、ネイティブマップとは明らかに異なります。これは、Go言語標準ライブラリのメンバーにすぎず、言語レベルのものではありません。このため、Go言語コンパイラはそのキーと値に対して特別な型チェックを実行しません。
sync.Mapのすべてのメソッドに含まれるキーと値のタイプは、インターフェース{}です。これは空のインターフェースであり、すべてをカバーできます。したがって、プログラムでのキータイプと値タイプの正確さを保証する必要があります。
並行セキュリティディクショナリにはキータイプが必要ですか?
要件があります。キーの実際のタイプは、関数タイプ、辞書タイプ、およびスライスタイプにすることはできません。
Goのネイティブディクショナリの主要なタイプは、関数タイプ、ディクショナリタイプ、およびスライスタイプにすることはできません。
コンカレントセキュリティディクショナリで使用される記憶媒体はネイティブディクショナリであり、使用するネイティブディクショナリキータイプも包括的なインターフェイス{}であるため、実際のタイプを関数タイプ、ディクショナリタイプ、またはスライスタイプとして使用しないでください。同時セキュリティディクショナリを操作するためのキー値。
これらのキー値の実際のタイプはプログラムの実行中にのみ決定できるため、Go言語コンパイラはコンパイル時にそれらをチェックできません。キー値の実際のタイプが正しくない場合は、必ずパニックが発生します。したがって、ここで最初にしなければならないことは、上記のルールに違反しないことです。並行セキュリティディクショナリを操作するたびに、キー値の実際のタイプを明示的に確認する必要があります。これは、保存、取得、削除のいずれの場合にも当てはまります。
同じコンカレントセキュリティディクショナリに対するこれらすべての操作を収集し、統一された方法で検査コードを記述します。さらに、コンカレントセキュリティディクショナリを構造タイプにカプセル化することは、多くの場合良い選択です。
キーのタイプが比較可能(または検証可能)であることを確認する必要があります。わからない場合は、最初に、reflect.TypeOf関数(つまり、reflect.Typeタイプの値)を呼び出してキー値に対応するリフレクションタイプの値を取得し、次にこの値のComparableメソッドを呼び出して正確な判断を行うことができます。 。
同時セキュリティディクショナリで正しいタイプのキーと値を確認するにはどうすればよいですか?
簡単に言うと、型のアサーション式またはリフレクション操作を使用して、型の正確性を保証できます。
最初の解決策は、並行セキュリティディクショナリに特定のタイプのキーのみを保存させることです。特定のタイプのキーと値を完全に判別できる状況に適用できます。たとえば、ここで指定するキーは、int型、文字列、または特定のタイプの構造のみにすることができます。キーのタイプが完全に判別されたら、タイプアサーション式を使用して、保存、フェッチ、および削除操作を実行するときにキーのタイプを確認できます。一般に、このチェックは面倒ではありません。さらに、コンカレントセキュリティディクショナリを構造タイプにカプセル化するとより便利です。これで、Go言語コンパイラーに型チェックを支援させることができます。コードは次のとおりです。
type IntStrMap struct {
m sync.Map
}
func (iMap *IntStrMap) Delete(key int) {
iMap.m.Delete(key)
}
func (iMap *IntStrMap) Load(key int) (value string, ok bool) {
v, ok := iMap.m.Load(key)
if v != nil {
value = v.(string)
}
return
}
func (iMap *IntStrMap) LoadOrStore(key int, value string) (actual string, loaded bool) {
a, loaded := iMap.m.LoadOrStore(key, value)
actual = a.(string)
return
}
func (iMap *IntStrMap) Range(f func(key int, value string) bool) {
f1 := func(key, value interface{}) bool {
return f(key.(int), value.(string))
}
iMap.m.Range(f1)
}
func (iMap *IntStrMap) Store(key int, value string) {
iMap.m.Store(key, value)
}
IntStrMapという名前の構造体型が作成されました。これは、キーの型がintで値の型が文字列の同時セキュリティディクショナリを表します。この構造タイプには、sync.Mapタイプのフィールドmが1つだけあります。また、このタイプのすべてのメソッドは、sync.Mapタイプのメソッドと非常によく似ています。
対応するメソッド名はまったく同じです。メソッドのシグネチャは非常に似ていますが、パラメータのタイプと、キーと値に関連する結果が異なる点が異なります。IntStrMap型のメソッドシグネチャでは、キーの型がintであり、値の型が文字列であることは明らかです。
sync.Mapタイプの元の柔軟性を維持したい場合は、キーと値のタイプを制限する必要もあります。
解決策2:カプセル化された構造タイプのすべてのメソッドは、sync.Mapタイプのメソッド(メソッド名とメソッドシグネチャを含む)と完全に一致させることができます。
型チェックを行うには、コードを追加する必要があります。
キーのタイプと値のタイプは、初期化時に完全に決定する必要があり、キーのタイプは比較可能です。
type ConcurrentMap struct {
m sync.Map
keyType reflect.Type
valueType reflect.Type
}