gpt4 book ai didi

c# - 锁定内部字符串?

转载 作者:可可西里 更新时间:2023-11-01 03:05:20 26 4
gpt4 key购买 nike

更新:如果此方法不是线程安全的,这是可以接受的,但我有兴趣了解如何使其成为线程安全的。另外,如果可以避免,我不想为 key 的所有值锁定单个对象。

原始问题:假设我想编写一个高阶函数,它接受一个键和一个函数,并检查是否已使用给定键缓存了一个对象。如果有,则返回缓存的值。否则,运行给定的函数并缓存并返回结果。

这是我的代码的简化版本:

public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
object cache = HttpContext.Current.Cache.Get(key);
//clearly not thread safe, two threads could both evaluate the below condition as true
//what can I lock on since the value of "key" may not be known at compile time?
if (cache == null)
{
T result = fn();
HttpContext.Current.Cache.Insert(key, result, null, expires, Cache.NoSlidingExpiration);
return result;
}
else
return (T)cache;
}

此外,假设我不知道编译时 key 的所有可能值。

如何使这个线程安全?我知道我需要在这里引入锁定,以防止 1+ 线程将我的条件评估为 true,但我不知道要锁定什么。我读过的许多关于锁定的示例(例如 Jon Skeet's article )都建议使用仅用于锁定的“虚拟”私有(private)变量。在这种情况下这是不可能的,因为 key 在编译时是未知的。我知道我可以通过为每个 key 使用相同的锁来简单地使这个线程安全,但这可能是一种浪费。

现在,我的主要问题是:

是否可以锁定key?字符串实习在这里有帮助吗?

看完.NET 2.0 string interning inside out ,我知道我可以显式调用 String.Intern() 以获得从字符串值到字符串实例的 1 对 1 映射。 这个适合加锁吗?我们把上面的代码改成:

public static T CheckCache<T>(string key, Func<T> fn, DateTime expires)
{
//check for the scenario where two strings with the same value are stored at different memory locations
key = String.Intern(key);
lock (key) //is this object suitable for locking?
{
object cache = HttpContext.Current.Cache.Get(key);
if (cache == null)
{
T result = fn();
HttpContext.Current.Cache.Insert(key, result, null, expires, Cache.NoSlidingExpiration);
return result;
}
else
return (T)cache;
}
}

上面的实现线程安全吗?

最佳答案

@wsanville 自己解决方案的问题,之前部分提到过:

  1. 您的代码库的其他部分可能出于不同目的锁定相同的内部字符串实例,如果幸运的话,只会导致性能问题,如果不幸的话会导致死锁(可能只会在未来发生,因为代码base 增长,由不知道您的 String.Intern 锁定模式的编码人员扩展) - 请注意,这包括对同一 interned 字符串的锁定 even if they are in different AppDomains ,可能导致跨 AppDomain 死锁
  2. 如果您决定这样做,您不可能收回内部内存
  3. String.Intern() 很慢

要解决所有这 3 个问题,您可以实现自己的 Intern() 将其绑定(bind)到您的特定锁定目的,即不要将其用作全局,通用字符串内部:

private static readonly ConcurrentDictionary<string, string> concSafe = 
new ConcurrentDictionary<string, string>();
static string InternConcurrentSafe(string s)
{
return concSafe.GetOrAdd(s, String.Copy);
}

我将此方法称为 ...Safe(),因为在实习时我不会存储传入的 String 实例,因为这可能例如是一个已经实习的 String,使其受到上面 1. 中提到的问题的影响。

为了比较各种驻留字符串方式的性能,我还尝试了以下两种方法,以及String.Intern

private static readonly ConcurrentDictionary<string, string> conc = 
new ConcurrentDictionary<string, string>();
static string InternConcurrent(string s)
{
return conc.GetOrAdd(s, s);
}

private static readonly Dictionary<string, string> locked =
new Dictionary<string, string>(5000);
static string InternLocked(string s)
{
string interned;
lock (locked)
if (!locked.TryGetValue(s, out interned))
interned = locked[s] = s;
return interned;
}

基准

100 个线程,每个线程随机选择 5000 个不同的字符串(每个字符串包含 8 个数字)中的一个 50000 次,然后调用各自的 intern 方法。充分预热后的所有值。这是 4 核 i5 上的 64 位 Windows 7。

注意预热上述设置意味着在预热之后,不会对相应的实习字典进行任何写入,而只有读取。这是我对手头的用例感兴趣的内容,但不同的写入/读取比率可能会影响结果。

结果

  • String.Intern():2032 毫秒
  • InternLocked():1245 毫秒
  • InternConcurrent():458 毫秒
  • InternConcurrentSafe():453 毫秒

InternConcurrentSafeInternConcurrent 一样快的事实是有道理的,因为这些数字是在预热之后(见上文 N.B.),所以有事实上,在测试期间没有或只有几次调用 String.Copy


为了正确封装它,创建一个这样的类:

public class StringLocker
{
private readonly ConcurrentDictionary<string, string> _locks =
new ConcurrentDictionary<string, string>();

public string GetLockObject(string s)
{
return _locks.GetOrAdd(s, String.Copy);
}
}

并且在为您可能拥有的每个用例实例化一个 StringLocker 之后,它就像调用一样简单

lock(myStringLocker.GetLockObject(s))
{
...

注意

再想一想,如果您只想锁定它,不需要返回类型为 string 的对象,因此完全没有必要复制字符,并且以下将比上面的类表现更好。

public class StringLocker
{
private readonly ConcurrentDictionary<string, object> _locks =
new ConcurrentDictionary<string, object>();

public object GetLockObject(string s)
{
return _locks.GetOrAdd(s, k => new object());
}
}

关于c# - 锁定内部字符串?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/6983714/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com