1

私はスレッドセーフな多値辞書に取り組んでいます。内部的に、このディクショナリはカスタム リンクリストを値として持つコンカレント ディクショナリ (.net 4.0) を使用します。同じキー項目がリンクリストに追加されます。問題は、コンカレント ディクショナリの AddOrUpdate メソッド(アプローチ 1)を使用して項目を挿入すると、TryGetValue メソッドを使用してキーが存在するかどうかを確認してから値を追加または更新する場合と比較して、コードの実行が少し遅くなることです。手動でロック内(アプローチ 2)。最初のアプローチを使用すると、300 万レコードを挿入するのに約 20 秒かかりますが、2 番目のアプローチを使用すると、同じマシン (Intel i3 第 2 世代 2.2 GHz & 4 Gb RAM) で約 9.5 秒かかります。私がまだ理解できない何かが欠けているに違いありません。

並行辞書のコードもチェックしましたが、ロック内で行っているのと同じことをしているようです:

public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
    {
        if (key == null) throw new ArgumentNullException("key"); 
        if (addValueFactory == null) throw new ArgumentNullException("addValueFactory");
        if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); 

        TValue newValue, resultingValue;
        while (true) 
        {
            TValue oldValue;
            if (TryGetValue(key, out oldValue))
            //key exists, try to update 
            {
                newValue = updateValueFactory(key, oldValue); 
                if (TryUpdate(key, newValue, oldValue)) 
                {
                    return newValue; 
                }
            }
            else //try add
            { 
                newValue = addValueFactory(key);
                if (TryAddInternal(key, newValue, false, true, out resultingValue)) 
                { 
                    return resultingValue;
                } 
            }
        }
    }

スレッド セーフな多値ディクショナリのコードを次に示します (アプローチ 2 はコメント化されています。コメントを外して違いを確認してください)。

更新:下に貼り付けていない削除、追加、およびその他のメソッドもあります。

class ValueWrapper<U, V>
{
    private U _key;
    private V _value;

    public ValueWrapper(U key, V value)
    {
        this._key = key;
        this._value = value;
    }

    public U Key
    {
        get { return _key; }
    }

    public V Value
    {
        get { return _value; }
        set { _value = value; }
    }
}

class LinkNode<Type>
{
    public LinkNode(Type data)
    {
        Data = data;
    }
    public LinkNode<Type> Next;
    public Type Data;
}

public class SimpleLinkedList<T> 
{
    #region Instance Member Variables
    private LinkNode<T> _startNode = null;
    private LinkNode<T> _endNode = null;
    private int _count = 0;

    #endregion

    public void AddAtLast(T item)
    {
        if (_endNode == null)
            _endNode = _startNode = new LinkNode<T>(item);
        else
        {
            LinkNode<T> node = new LinkNode<T>(item);
            _endNode.Next = node;
            _endNode = node;
        }

        _count++;
    }

    public T First
    {
        get { return _startNode == null ? default(T) : _startNode.Data; }
    }

    public int Count
    {
        get { return _count; }
    }

}

class MultiValThreadSafeDictionary<U, T>
{
    private ConcurrentDictionary<U, SimpleLinkedList<ValueWrapper<U, T>>> _internalDictionary;

    private ReaderWriterLockSlim _slimLock = new ReaderWriterLockSlim();


    public MultiValThreadSafeDictionary()
    {
        _internalDictionary = new ConcurrentDictionary<U, SimpleLinkedList<ValueWrapper<U, T>>>(2, 100);
    }

    public T this[U key]
    {
        get
        {
            throw new NotImplementedException();
        }
        set
        {
            /* ****Approach 1 using AddOrUpdate**** */


            _internalDictionary.AddOrUpdate(key, (x) =>
            {
                SimpleLinkedList<ValueWrapper<U, T>> list = new SimpleLinkedList<ValueWrapper<U, T>>();
                ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
                list.AddAtLast(vw);
                //_internalDictionary[key] = list;

                return list;
            },

            (k, existingList) =>
            {
                try
                {
                    _slimLock.EnterWriteLock();

                    if (existingList.Count == 0)
                    {
                        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
                        existingList.AddAtLast(vw);
                    }
                    else
                        existingList.First.Value = value;

                    return existingList;
                }
                finally
                {
                    _slimLock.ExitWriteLock();
                }
            });


            /* ****Approach 2 not using AddOrUpdate**** */

            /*
            try
            {
                _slimLock.EnterWriteLock();

                SimpleLinkedList<ValueWrapper<U, T>> list;
                if (!_internalDictionary.TryGetValue(key, out list))
                {
                    list = new SimpleLinkedList<ValueWrapper<U, T>>();
                    ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);

                    list.AddAtLast(vw);

                    _internalDictionary[key] = list;
                    //_iterator.AddAtLast(vw);
                    return;
                }

                if (list.Count == 0)
                {
                    ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
                    list.AddAtLast(vw);
                    //_iterator.AddAtLast(vw);
                }
                else
                    list.First.Value = value;
            }
            finally
            {
                _slimLock.ExitWriteLock();
            }
            */

        }
    }
}

テスト コードは項目を挿入するだけで、すべて一意のキーを持ちます。以下の通りです。

MultiValThreadSafeDictionary<string, int> testData = new MultiValThreadSafeDictionary<string, int>();

    Task t1 = new Task(() =>
        {
            for (int i = 0; i < 1000000; i++)
            {
                testData[i.ToString()] = i;
            }
        }
    );

    Task t2 = new Task(() =>
    {
        for (int i = 1000000; i < 2000000; i++)
        {
            testData[i.ToString()] = i;
        }
    }
    );

    Task t3 = new Task(() =>
    {
        for (int i = 2000000; i < 3000000; i++)
        {
            testData[i.ToString()] = i;
        }
    }
    );

    Stopwatch watch = new Stopwatch();
    watch.Start();

    t1.Start();
    t2.Start();
    t3.Start();

    t1.Wait();
    t2.Wait();
    t3.Wait();

    watch.Stop();

    Console.WriteLine("time taken:" + watch.ElapsedMilliseconds);

更新 1:

「280Z28」からの回答に基づいて、質問を言い換えています。GetOrAdd と 'my' メソッドがほぼ同時にかかるのはなぜですか。私のメソッドのように、余分なロックを取得し、TryAndGet メソッドも呼び出しています。また、AddOrGet と比較して AddOrUpdate に 2 倍の時間がかかるのはなぜですか。すべてのアプローチのコードは次のとおりです。

ConcurrentDictionary (.net 4) の GetOrAdd および AddOrUpdate メソッドには、次のコードがあります。

public TValue GetOrAdd(TKey key, TValue value)
{
    if (key == null) throw new ArgumentNullException("key");
    TValue resultingValue;
    TryAddInternal(key, value, false, true, out resultingValue); 
    return resultingValue; 
}

public TValue AddOrUpdate(TKey key, Func<TKey, TValue> addValueFactory, Func<TKey, TValue, TValue> updateValueFactory)
{
    if (key == null) throw new ArgumentNullException("key"); 
    if (addValueFactory == null) throw new ArgumentNullException("addValueFactory");
    if (updateValueFactory == null) throw new ArgumentNullException("updateValueFactory"); 

    TValue newValue, resultingValue;
    while (true) 
    {
        TValue oldValue;
        if (TryGetValue(key, out oldValue))
        //key exists, try to update 
        {
            newValue = updateValueFactory(key, oldValue); 
            if (TryUpdate(key, newValue, oldValue)) 
            {
                return newValue; 
            }
        }
        else //try add
        { 
            newValue = addValueFactory(key);
            if (TryAddInternal(key, newValue, false, true, out resultingValue)) 
            { 
                return resultingValue;
            } 
        }
    }
}

私のコードで GetOrAdd は次のように使用されます (9 秒かかります):

SimpleLinkedList<ValueWrapper<U, T>> existingList = new SimpleLinkedList<ValueWrapper<U, T>>();
existingList = _internalDictionary.GetOrAdd(key, existingList);
try
{
    _slimLock.EnterWriteLock();

    if (existingList.Count == 0)
    {
        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
        existingList.AddAtLast(vw);
    }
    else
        existingList.First.Value = value;
}
finally
{
    _slimLock.ExitWriteLock();
}

AddOrUpdate は次のように使用されます (すべての追加に 20 秒かかり、更新はありません)。回答の1つで説明されているように、このアプローチは更新には適していません。

_internalDictionary.AddOrUpdate(key, (x) =>
{
    SimpleLinkedList<ValueWrapper<U, T>> list = new SimpleLinkedList<ValueWrapper<U, T>>();
    ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
    list.AddAtLast(vw);
    return list;
},

(k, existingList ) =>
{
    try
    {
        _slimLock.EnterWriteLock();

        if (existingList.Count == 0)
        {
            ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
            existingList.AddAtLast(vw);
        }
        else
            existingList.First.Value = value;

        return existingList;
    }
    finally
    {
        _slimLock.ExitWriteLock();
    }
});

AddOrGet と AddOrUpdate を使用しないコードは次のとおりです (9.5 秒かかります)。

try
{
    _slimLock.EnterWriteLock();

    VerySimpleLinkedList<ValueWrapper<U, T>> list;
    if (!_internalDictionary.TryGetValue(key, out list))
    {
        list = new VerySimpleLinkedList<ValueWrapper<U, T>>();
        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);

        list.AddAtLast(vw);

        _internalDictionary[key] = list;
        return;
    }

    if (list.Count == 0)
    {
        ValueWrapper<U, T> vw = new ValueWrapper<U, T>(key, value);
        list.AddAtLast(vw);
    }
    else
        list.First.Value = value;
}
finally
{
    _slimLock.ExitWriteLock();
}
4

2 に答える 2

1

AddOrUpdateこのコードには使用しないでください。update メソッドが格納されている値を実際に更新することは決してないため、これは非常に明確です。常に引数を変更ConcurrentDictionaryせずに返します。existingList代わりに、次のようなことを行う必要があります。

SimpleLinkedList<ValueWrapper<U, T>> list = _internalDictionary.GetOrAdd(key, CreateEmptyList);
// operate on list here

...

private static SimpleLinkedList<ValueWrapper<U, T>> CreateEmptyList()
{
    return new SimpleLinkedList<ValueWrapper<U, T>>();
}
于 2013-04-30T13:22:20.493 に答える
0

ディクショナリに対する読み取り操作は、ロックなしで実行されます。http://msdn.microsoft.com/en-us/library/dd287191.aspxで述べたように

AddOrUpdate の実装では、項目が既に存在するかどうかを確認するために細粒度のロックを使用していますが、最初に自分で読み取るときは、ロックなしで読み取る方が高速であり、既存の項目に必要なロックを減らすことができます。

于 2013-04-30T08:13:33.710 に答える