好的,我只是无法正确理解多线程场景。很抱歉再次提出类似的问题,我只是在互联网上看到了许多不同的“事实”。
public static class MyClass {
private static List _myList = new List;
private static bool _record;
public static void StartRecording()
{
_myList.Clear();
_record = true;
}
public static IEnumerable StopRecording()
{
_record = false;
// Return a Read-Only copy of the list data
var result = new List(_myList).AsReadOnly();
_myList.Clear();
return result;
}
public static void DoSomething()
{
if(_record) _myList.Add("Test");
// More, but unrelated actions
}
}
这个想法是,如果 Recording 被激活,对 DoSomething() 的调用会被记录在一个内部 List 中,并在 StopRecording() 被调用时返回。
我的规格是这样的:
- StartRecording 不被认为是线程安全的。用户应该在没有其他线程调用 DoSomething() 时调用它。但如果能以某种方式实现,那就太好了。
- StopRecording 也不是正式的线程安全的。同样,如果可以的话,那就太好了,但这不是必需的。
- DoSomething 必须是线程安全的
通常的方法似乎是:
public static void DoSomething()
{
object _lock = new object();
lock(_lock){
if(_record) _myList.Add("Test");
}
// More, but unrelated actions
}
或者,声明一个静态变量:
private static object _lock;
public static void DoSomething()
{
lock(_lock){
if(_record) _myList.Add("Test");
}
// More, but unrelated actions
}
但是,这个答案说这不会阻止其他代码访问它。
所以我想知道
- 我将如何正确锁定列表?
- 我应该在我的函数中创建锁对象还是作为静态类变量?
- 我也可以将 Start 和 StopRecording 的功能包装在一个锁块中吗?
- StopRecording() 做了两件事:将布尔变量设置为 false(以防止 DoSomething() 添加更多内容),然后复制列表以将数据副本返回给调用者)。我假设 _record = false; 是原子的并且会立即生效吗?所以通常我根本不用担心这里的多线程,除非其他一些线程再次调用 StartRecording()?
在一天结束时,我正在寻找一种方式来表达“好的,这个列表现在是我的,所有其他线程都必须等到我完成它”。
最佳解决方案
我将在这里锁定 _myList 本身,因为它是私有的,但使用单独的变量更常见。改进几点:
请注意,此代码用于
lock(_myList)
同步对 _myList和_record 的访问。您需要同步这两个上的所有操作。并且同意这里的其他答案,
lock(_myList)
对 _myList 没有任何作用,它只是使用 _myList 作为令牌(大概作为 HashSet 中的键)。所有方法都必须通过使用相同令牌请求许可来公平竞争。另一个线程上的方法仍然可以使用 _myList 而无需先锁定,但会产生不可预知的结果。我们可以使用任何令牌,所以我们经常专门创建一个:
然后使用
lock(_listLock)
而不是lock(_myList)
到处使用。如果 myList 是公开的,那么这种技术是可取的,如果您重新创建了 myList 而不是调用 Clear(),这将是绝对必要的。