了解 List<T> 的线程安全
Posted
技术标签:
【中文标题】了解 List<T> 的线程安全【英文标题】:Understanding the thread safety of a List<T> 【发布时间】:2021-11-26 20:01:45 【问题描述】:-
我试图理解为什么它会打印第 0 项、第 0 项和第 1 项
在调试时会打印第 0 项、第 0 项、第 1 项、第 1 项
2 结果中的项目符号上方有意义。有人可以帮我理解为什么它会在 1 中打印项目符号吗?
简而言之,取自 C# 9.0
class ThreadSafe
static List<string> _list = new List<string>();
public static void AddItem()
// lock the list
lock (_list)
_list.Add("Item " + _list.Count);
// Rather than locking for the duration; copy to an array
string[] items;
lock (_list)
items = _list.ToArray();
foreach (string s in items)
Console.WriteLine(s);
static void Main(string[] args)
new Thread(ThreadSafe.AddItem).Start();
new Thread(ThreadSafe.AddItem).Start();
【问题讨论】:
输出不会取决于运行线程的计算机的速度和调度吗? 我只看到“项目 1”的一个实例 dotnetfiddle.net/8oHjcz 如果您在打印中包含线程号,代码是否有意义? 您能否说明您希望输出的哪一部分有所不同以及为什么?也许您希望一次性执行对Console.WriteLine
的所有单独调用? (@gunr2171 建议在所有打印语句中添加线程号可以帮助您澄清您的要求,甚至完全回答)
使用 ThreadID,我可以看到第一个线程打印项目 0,然后第二个线程打印项目 0。然后第二个线程打印项目 1,并完成执行。我试图理解为什么第二个线程在第一个线程执行后打印项目 0。好像是同时进行的。
老实说,约瑟夫·阿尔巴哈里书中的这个例子不是很好。产生的输出令人困惑。我希望他们能在下一个版本的书中改进它!
【参考方案1】:
https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/lock-statement
当持有锁时,持有锁的线程可以再次获取和释放锁。任何其他线程都被阻止获取锁并等待直到锁被释放。
这确实取决于环境,但直截了当的答案如下。
-
线程 1 将锁定列表。
线程 2 将尝试访问但无法访问,因此它将等待
线程 1 将添加到列表中
线程 2 仍在等待
线程 1 将释放锁
线程 2 将锁定列表
线程 1 将尝试锁定列表,但它已被锁定,因此它将等待
等等
这真的取决于线程何时真正开始计算。但这将是您所看到的输出的最合乎逻辑的答案。在不同的环境(CPU 等)上运行它可能会导致不同的输出。
【讨论】:
以上是关于了解 List<T> 的线程安全的主要内容,如果未能解决你的问题,请参考以下文章
了解 Rust 中的线程安全 RwLock<Arc<T>> 机制
Collections.synchronizedList()