English 中文(简体)
避免使用“当前审慎”法(GetOr Add())时的中途(有腐败行为)数据
原标题:Avoiding stale (logically corrupt) data when using "ConcurrentDictionary.GetOrAdd()", Repro code included

条底线描述了如何利用GetOrAdd(如果我正确理解)造成腐败/不投机的结果。

<><>>

ConcurrentDictionary is designed for multithreaded scenarios. You do not have to use locks in your code to add or remove items from the collection. However, it is always possible for one thread to retrieve a value, and another thread to immediately update the collection by giving the same key a new value.

Also, although all methods of ConcurrentDictionary are thread-safe, not all methods are atomic, specifically GetOrAdd and AddOrUpdate. The user delegate that is passed to these methods is invoked outside of the dictionary s internal lock. (This is done to prevent unknown code from blocking all threads.) Therefore it is possible for this sequence of events to occur:

1) threadA calls GetOrAdd, finds no item and creates a new item to Add by invoking the valueFactory delegate.

2) threadB calls GetOrAdd concurrently, its valueFactory delegate is invoked and it arrives at the internal lock before threadA, and so its new key-value pair is added to the dictionary.

3) threadA s user delegate completes, and the thread arrives at the lock, but now sees that the item exists already

4) threadA performs a "Get", and returns the data that was previously added by threadB.

Therefore, it is not guaranteed that the data that is returned by GetOrAdd is the same data that was created by the thread s valueFactory. A similar sequence of events can occur when AddOrUpdate is called.

<<><><><><>>>>><><><>>>>>

如何正确核实数据并重新统计更新? 冰冰箱法是一种根据旧价值内容尝试/重新操作的延伸方法。

如何执行? 我能否以结果(<>>verification> /<>>)为有效状态,或者我是否必须利用一种不同的方法重新掌握这些价值观?

Code

以下法典在更新价值观时具有种族条件。 理想的行为是,AddOrUpdateWithoutRetrieving()将以不同方式(使用++Interlock (<>)。 ()。

我还希望在一个单位开展多个外地业务,如果以前的更新表明,由于种族状况,“收受”,则对最新情况进行重新研究。

Run the code and you will see each value appear in the console start out increasing by one, but each of the values will drift and some will be a few iterations ahead/behind.

namespace DictionaryHowTo
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;

    // The type of the Value to store in the dictionary:
    class FilterConcurrentDuplicate
    {
        // Create a new concurrent dictionary.
        readonly ConcurrentDictionary<int, TestData> eventLogCache = 
             new ConcurrentDictionary<int, TestData>();

        static void Main()
        {
            FilterConcurrentDuplicate c = new FilterConcurrentDuplicate();

            c.DoRace(null);
        }

        readonly ConcurrentDictionary<int, TestData> concurrentCache = 
            new ConcurrentDictionary<int, TestData>();
        void DoRace(string[] args)
        {
            int max = 1000;

            // Add some key/value pairs from multiple threads.
            Task[] tasks = new Task[3];

            tasks[0] = Task.Factory.StartNew(() =>
            {

                System.Random RandNum = new System.Random();
                int MyRandomNumber = RandNum.Next(1, 500);

                Thread.Sleep(MyRandomNumber);
                AddOrUpdateWithoutRetrieving();

            });

            tasks[1] = Task.Factory.StartNew(() =>
            {
                System.Random RandNum = new System.Random();
                int MyRandomNumber = RandNum.Next(1, 1000);

                Thread.Sleep(MyRandomNumber);

                AddOrUpdateWithoutRetrieving();

            });

            tasks[2] = Task.Factory.StartNew(() =>
            {
                AddOrUpdateWithoutRetrieving();

            });
            // Output results so far.
            Task.WaitAll(tasks);

            AddOrUpdateWithoutRetrieving();

            Console.WriteLine("Press any key.");
            Console.ReadKey();
        }
        public class TestData : IEqualityComparer<TestData>
        {
            public string aStr1 { get; set; }
            public Guid? aGud1 { get; set; }
            public string aStr2 { get; set; }
            public int aInt1 { get; set; }
            public long? aLong1 { get; set; }

            public DateTime aDate1 { get; set; }
            public DateTime? aDate2 { get; set; }

            //public int QueryCount { get; set; }
            public int QueryCount = 0;//

            public string zData { get; set; }
            public bool Equals(TestData x, TestData y)
            {
                return x.aStr1 == y.aStr1 &&
                    x.aStr2 == y.aStr2 &&
                       x.aGud1 == y.aGud1 &&
                       x.aStr2 == y.aStr2 &&
                       x.aInt1 == y.aInt1 &&
                       x.aLong1 == y.aLong1 &&
                       x.aDate1 == y.aDate1 &&
                       x.QueryCount == y.QueryCount ;
            }

            public int GetHashCode(TestData obj)
            {
                TestData ci = (TestData)obj;
                // http://stackoverflow.com/a/263416/328397
                return 
                  new { 
                         A = ci.aStr1, 
                         Aa = ci.aStr2, 
                         B = ci.aGud1, 
                         C = ci.aStr2, 
                         D = ci.aInt1, 
                         E = ci.aLong1, 
                         F = ci.QueryCount , 
                         G = ci.aDate1}.GetHashCode();
            }
        }
        private   void AddOrUpdateWithoutRetrieving()
        {
            // Sometime later. We receive new data from some source.
            TestData ci = new TestData() 
            { 
              aStr1 = "Austin", 
              aGud1 = new Guid(), 
              aStr2 = "System", 
              aLong1 = 100, 
              aInt1 = 1000, 
              QueryCount = 0, 
              aDate1 = DateTime.MinValue
            };

            TestData verify = concurrentCache.AddOrUpdate(123, ci,
                (key, existingVal) =>
                {
                    existingVal.aStr2 = "test1" + existingVal.QueryCount;
                    existingVal.aDate1 = DateTime.MinValue;
                    Console.WriteLine
                     ("Thread:" + Thread.CurrentThread.ManagedThreadId + 
                          "  Query Count A:" + existingVal.QueryCount);
                    Interlocked.Increment(ref existingVal.QueryCount);
                    System.Random RandNum = new System.Random();
                    int MyRandomNumber = RandNum.Next(1, 1000);

                    Thread.Sleep(MyRandomNumber);
                    existingVal.aInt1++;
                    existingVal.aDate1 = 
                         existingVal.aDate1.AddSeconds
                         (existingVal.aInt1);  
                    Console.WriteLine(
                          "Thread:" + Thread.CurrentThread.ManagedThreadId + 
                           "  Query Count B:" + existingVal.QueryCount);
                    return existingVal;
                });


            // After each run, every value here should be ++ the previous value
            Console.WriteLine(
                "Thread:"+Thread.CurrentThread.ManagedThreadId + 
                 ": Query Count returned:" + verify.QueryCount + 
                 " eid:" + verify.aInt1 + " date:" +  
                 verify.aDate1.Hour + " "  + verify.aDate1.Second + 
                 " NAME:" + verify.aStr2
                );
        }

    }
}

<>http://europa-eu>

Thread:12: Query Count returned:0 eid:1000 date:0 0 NAME:System

Thread:12  Query Count A:0
Thread:13  Query Count A:1
Thread:12  Query Count B:2
Thread:12: Query Count returned:2 eid:1001 date:0 41 NAME:test11

Thread:12  Query Count A:2
Thread:13  Query Count B:3
Thread:13: Query Count returned:3 eid:1002 date:0 42 NAME:test12

Thread:13  Query Count A:3
Thread:11  Query Count A:4
Thread:11  Query Count B:5
Thread:11: Query Count returned:5 eid:1003 date:0 43 NAME:test14

Thread:11  Query Count A:5
Thread:13  Query Count B:6
Thread:13: Query Count returned:6 eid:1004 date:0 44 NAME:test15

......

Thread:11  Query Count A:658
Thread:11  Query Count B:659
Thread:11: Query Count returned:659 eid:1656 date:0 36 NAME:test1658

Thread:11  Query Count A:659
Thread:11  Query Count B:660
Thread:11: Query Count returned:660 eid:1657 date:0 37 NAME:test1659

Thread:11  Query Count A:660
Thread:11  Query Count B:661
Thread:11: Query Count returned:661 eid:1658 date:0 38 NAME:test1660

Thread:11  Query Count A:661
Thread:11  Query Count B:662
Thread:11: Query Count returned:662 eid:1659 date:0 39 NAME:test1661

在这项法典中,“eid”的数值应比Kery的数值高出1 000多,但就变数而言,两者之间的差异在1至7之间。 这种不一致可能导致一些申请失败或报告不正确的数据。

最佳回答

本意见依据的是对“How to: Add and Remove items from a Con CurrentDictionary 。 和基本一致系数——共同物体的平行非组别修改。

首先,请澄清相关条款的真实含义。 页: 1 GetOrAdd为等值。

Say, you call AddOrUpdate from several threads and specify the same key. Assume that an entry with that key already exists. Each thread will come along, notice that there is already an entry with the specified key and that the Update part of AddOrUpdate is relevant. In doing so, no thread will lock the dictionary. Instead it will use some interlocked instructions to atomically check if an entry-key exists or not.

因此,我们的几条镜头都注意到,钥匙存在,需要称作<代码>更新日期:。 该代表通过<代码>AddOrUpdate;该代表参考了现有的关键和价值,并回报了更新价值。 现在,所涉的所有线索将同时把工厂称作工厂。 之后,它们将按以前不知的顺序完成,每一条路面将尝试利用原子操作(使用隔锁指示)来取代现值,将其计算得当。 无法知道什么是“双向的”。 赢利的read子可以储存其计算价值。 其他人将注意到,词典的价值已不再是作为其论点列入<代码>updateValueFactory的价值。 面对这一现实,他们将放弃这一行动,放弃公正的计算价值。 这正是你想要做到的。

其次,请澄清一下,在管理此处列出的法典样本时,你为什么会获得奇怪的价值观:

Recall that the updateValueFactory delegate passed to AddOrUpdate takes REFERENCES the existing key and value and returns the update-value. The code sample in its AddOrUpdateWithoutRetrieving() method starts performing operations directly o that reference. Instead of creating a new replacement value and modifying THAT, it modifies instance member values of existingVal – an object that is already in the dictionary – and then simply returns that reference. And it does that not atomically – it reads some values, updates some values, reads more, updates more. Of course, we have seen above that this happens on several threads concurrently – they all modify the SAME object. No wonder the result is that at any one time (when the code sample calls WriteLine), the object contains member instance values that originated from different threads.

The dictionary has nothing to do with this – the code simply modifies an object that is shared between threads non-atomically. This is one of the most common concurrency bugs around. The two most common workarounds depend on the scenario. Either use a shared lock to make the entire object modification atomic, or first atomically copy the entire object and then modify the local copy.

后者试图在<条码>中添加:

private Object _copyLock = null;
 
private Object GetLock() {
 
    if (_copyLock != null)
        return _copyLock;
 
    Object newLock = new Object();
    Object prevLock = Interlocked.CompareExchange(ref _copyLock, newLock, null);
    return (prevLock == null) ? newLock : prevLock;
}
 
public TestData Copy() {
 
    lock (GetLock()) {
        TestData copy = new TestData();
        copy.aStr1 = this.aStr1;
        copy.aStr2 = this.aStr2;
        copy.aLong1 = this.aLong1;
        copy.aInt1 = this.aInt1;
        copy.QueryCount = this.QueryCount;
        copy.aDate1 = this.aDate1;
        copy.aDate2 = this.aDate2;
        copy.zData = this.zData;
 
        return copy;
    }
}

然后修改工厂如下:

TestData verify = concurrentCache.AddOrUpdate(123, ci,
    (key, existingVal) =>
    {
        TestData newVal = existingVal.Copy();
        newVal.aStr2 = "test1" + newVal.QueryCount;
        newVal.aDate1 = DateTime.MinValue;
        Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId + "  Query Count A:" + newVal.QueryCount);
        Interlocked.Increment(ref newVal.QueryCount);
        System.Random RandNum = new System.Random();
        int MyRandomNumber = RandNum.Next(1, 1000);
 
        Thread.Sleep(MyRandomNumber);
        newVal.aInt1++;
        newVal.aDate1 = newVal.aDate1.AddSeconds(newVal.aInt1);
        Console.WriteLine("Thread:" + Thread.CurrentThread.ManagedThreadId + "  Query Count B:" + newVal.QueryCount);
        return newVal;
    });

I hope this helps.

问题回答

或许正确的办法是,如果返还价值不是由<代码>价值代码>/代码>设定的,则不予考虑。 如果不能接受,你需要使用锁。

始终没有普遍的保护。 但是,一个共同的工作是交回一个<代码>Lazy<T>而不是一个<代码>T。 造成不必要 la的这种方式不会造成任何损害,因为所赢得的胜利就已经开始。 只有1个拉齐,才能成为与钥匙对应的最终价值。 只有一次特别的拉齐事件才会返回。





相关问题
Anyone feel like passing it forward?

I m the only developer in my company, and am getting along well as an autodidact, but I know I m missing out on the education one gets from working with and having code reviewed by more senior devs. ...

NSArray s, Primitive types and Boxing Oh My!

I m pretty new to the Objective-C world and I have a long history with .net/C# so naturally I m inclined to use my C# wits. Now here s the question: I feel really inclined to create some type of ...

C# Marshal / Pinvoke CBitmap?

I cannot figure out how to marshal a C++ CBitmap to a C# Bitmap or Image class. My import looks like this: [DllImport(@"test.dll", CharSet = CharSet.Unicode)] public static extern IntPtr ...

How to Use Ghostscript DLL to convert PDF to PDF/A

How to user GhostScript DLL to convert PDF to PDF/A. I know I kind of have to call the exported function of gsdll32.dll whose name is gsapi_init_with_args, but how do i pass the right arguments? BTW, ...

Linqy no matchy

Maybe it s something I m doing wrong. I m just learning Linq because I m bored. And so far so good. I made a little program and it basically just outputs all matches (foreach) into a label control. ...