我是否正确地说我只需要使用锁来添加/删除/更改列表,或者我还需要在迭代时锁定它?
所以我通过这样做是安全的:
class ItemsList
{
List<int> items = new List<int>();
object listLock = new object();
public void Add(int item)
{
lock (listLock)
{
items.Add(item);
}
}
public void Remove(int item)
{
lock (listLock)
{
items.Remove(item);
}
}
public void IncrementAll()
{
foreach (var item in items)
{
item += 1;
}
}
}
答案 0 :(得分:5)
当迭代它时你肯定会锁定 - 如果在迭代它时更改列表,则会抛出异常。
来自List<T>.GetEnumerator
的文档:
枚举器没有对集合的独占访问权限;因此,枚举通过集合本质上不是一个线程安全的过程。为了在枚举期间保证线程安全,您可以在整个枚举期间锁定集合。要允许多个线程访问集合以进行读写,您必须实现自己的同步。
此外,即使是List<T>
的单个读取也不是线程安全的,如果您也可以写入它 - 即使它没有失败,也无法保证您获得最多最近的价值。
基本上,List<T>
仅对于多个线程是安全的,如果在所有线程的状态变得可见的最后一个点之后没有写入它。
如果您想要一个线程安全的集合,并且如果您使用的是.NET 4或更高版本,请查看System.Collections.Concurrent
命名空间。
答案 1 :(得分:1)
List<T>
通常不是线程安全的。拥有多个阅读器不会导致任何问题,但是,在阅读时您无法写入列表。因此,您需要锁定读取和写入,或使用类似System.Threading.ReaderWriterLock(允许多个读取器,但只允许一个编写器)。如果您是在.NET 4.0 or bigger
下开发,则可以使用BlockingCollection代替,这是一个线程安全的集合。
答案 2 :(得分:0)
不,那不安全。如果另一个线程在您阅读时修改了它,您将获得“集合被修改”的异常类型。
解决此问题的最有效方法是使用ReaderWriterLockSlim来控制访问权限,以便多个线程可以同时读取它,并且只有在尝试修改它时才会被锁定。
答案 3 :(得分:0)
You're not even thread safe with what you have if you never iterate it
在我们讨论它是否能按预期工作之前,您需要定义您正在对数据结构执行的操作类型。
在一般情况下,您确实需要在阅读时锁定。实际上,有人可以在你进行迭代的过程中添加一个项目,它会破坏各种各样的东西。如果您在阅读过程中添加了一个项目,即使阅读单个项目也可能会被破坏。
另请注意,这最多会使每个操作在逻辑上都是原子的。如果您正在执行多个操作并对数据结构的状态做出假设,那么这还不够。
在许多情况下,要解决此问题,您需要在调用者端进行锁定,而不是仅将每个操作包装在lock
中。
答案 4 :(得分:0)
您应该使用ReaderWriterLockSlim
,以便多个线程可以读取集合,但只有一个可以修改它。
答案 5 :(得分:0)
您可能需要查看ConcurrentQueue&lt;&gt;();
这基本上是一个线程安全列表(据我所知),这是相当方便的。你可以使用它有点像这样;
public ConcurrentQueue<yourType> alarmQueue = new ConcurrentQueue<yourType>();
System.Timers.Timer timer;
public QueueManager()
{
timer = new System.Timers.Timer(1000);
timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
timer.Enabled = true;
}
void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
DeQueueAlarm();
}
private void DeQueueAlarm()
{
yourType yourtype;
while (alarmQueue.TryDequeue(out yourtype))
{
//dostuff
}
}
编辑:正如约翰所说,这可以在.Net4开始使用。在这里阅读更多; http://msdn.microsoft.com/en-us/library/dd267265.aspx
答案 6 :(得分:0)
在IncrementAll上,您将捕获InvalidOperationException,因为在集合中进行了更改。您可以在测试单元中看到它,如下所示:
ItemsList il = new ItemsList();
Task ts = new Task(() =>
{
for (int i = 0; i < 100000; i++)
{
il.Add(i);
System.Threading.Thread.Sleep(100);
}
}
);
ts.Start();
Task ts2 = new Task(() =>
{
//DoSomeActivity
il.IncrementAll();
}
);
ts2.Start();
Console.Read();
迭代也必须锁定!!!