c# - 在私有(private)字段中使用 Bcl ImmutableDictionary

标签 c# multithreading thread-safety immutability base-class-library

假设我有一个将从多个线程调用的类,并且我打算将一些数据存储在 ImmutableDictionary 中。在这个类的私有(private)字段中

public class Something {
    private ImmutableDictionary<string,string> _dict;
    public Something() {
       _dict = ImmutableDictionary<string,string>.Empty;
    }

    public void Add(string key, string value) {

       if(!_dict.ContainsKey(key)) {
          _dict = _dict.Add(key,value);
       }
    }
}

这会不会被多个线程以这样一种方式调用,以至于您会得到关于字典中已经存在的键的错误?

Thread1 检查字典发现 false Thread2 检查字典是否为假 Thread1 添加值并更新对 _dict 的引用 Thread2 增加了值,但它已经被添加了,因为它使用了相同的引用?

最佳答案

在使用不可变字典时,您绝对可以做到线程安全。数据结构本身是完全线程安全的,但您在多线程环境中对其应用更改必须小心编写,以避免在您自己的代码中丢失数据。

这是我经常在这种情况下使用的模式。它不需要锁,因为我们所做的唯一改变是单个内存分配。如果必须设置多个字段,则需要使用锁。

using System.Threading;

public class Something {
    private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;

    public void Add(string key, string value) {
       // It is important that the contents of this loop have no side-effects
       // since they can be repeated when a race condition is detected.
       do {
          var original = _dict;
          if (local.ContainsKey(key)) {
             return;
          }

          var changed = original.Add(key,value);
          // The while loop condition will try assigning the changed dictionary
          // back to the field. If it hasn't changed by another thread in the
          // meantime, we assign the field and break out of the loop. But if another
          // thread won the race (by changing the field while we were in an 
          // iteration of this loop), we'll loop and try again.
       } while (Interlocked.CompareExchange(ref this.dict, changed, original) != original);
    }
}

事实上,我经常使用这种模式,为此我定义了一个静态方法:

/// <summary>
/// Optimistically performs some value transformation based on some field and tries to apply it back to the field,
/// retrying as many times as necessary until no other thread is manipulating the same field.
/// </summary>
/// <typeparam name="T">The type of data.</typeparam>
/// <param name="hotLocation">The field that may be manipulated by multiple threads.</param>
/// <param name="applyChange">A function that receives the unchanged value and returns the changed value.</param>
public static bool ApplyChangeOptimistically<T>(ref T hotLocation, Func<T, T> applyChange) where T : class
{
    Requires.NotNull(applyChange, "applyChange");

    bool successful;
    do
    {
        Thread.MemoryBarrier();
        T oldValue = hotLocation;
        T newValue = applyChange(oldValue);
        if (Object.ReferenceEquals(oldValue, newValue))
        {
            // No change was actually required.
            return false;
        }

        T actualOldValue = Interlocked.CompareExchange<T>(ref hotLocation, newValue, oldValue);
        successful = Object.ReferenceEquals(oldValue, actualOldValue);
    }
    while (!successful);

    Thread.MemoryBarrier();
    return true;
}

您的 Add 方法会变得更加简单:

public class Something {
    private ImmutableDictionary<string, string> dict = ImmutableDictionary<string, string>.Empty;

    public void Add(string key, string value) {
       ApplyChangeOptimistically(
          ref this.dict,
          d => d.ContainsKey(key) ? d : d.Add(key, value));
    }
}

关于c# - 在私有(private)字段中使用 Bcl ImmutableDictionary,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14655248/

相关文章:

java - 让java多线程等待输入给定

android - 在 sqlite (Android) 中锁定数据库或表

c - 在多线程上下文中,随机生成器的替代方案与 erand48() 一样快?

c# - 远程服务器返回错误 : (401) Unauthorized

java - 在多线程中使用 RabbitMQ 发布者

multithreading - 是否有一个 API 可以让 N 个线程(或 N 个线程上的 N 个闭包)完成?

c++ - 包含线程局部变量的函数是可重入的吗?

c# - 删除 EWS EmailMessage 正文中的 HTML 部分

c# - 如何在 MVC5 中验证字典

C# - 添加、编辑和删除文件标签