2

我是否正确地说我只需要使用锁定来添加/删除/更改列表,还是在迭代它时也需要锁定它?

所以我这样做线程安全吗:

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;
        }
    }
}
4

7 回答 7

5

你也应该在迭代它时锁定 - 如果在你迭代它时更改了列表,则会引发异常。

从文档中List<T>.GetEnumerator

枚举器没有对集合的独占访问权;因此,通过集合进行枚举本质上不是线程安全的过程。为了保证枚举过程中的线程安全,可以在整个枚举过程中锁定集合。要允许集合被多个线程访问以进行读写,您必须实现自己的同步。

此外,如果您也可以对它进行写入,那么即使从 a 读取一次List<T>也不是线程安全的——即使它没有失败,也不能保证您会获得最新的值。

基本上,只有在它的状态对所有线程可见的最后一点之后没有被写入,它才对多个线程是安全的List<T>

如果您想要一个线程安全的集合,并且您使用的是 .NET 4 或更高版本,请查看System.Collections.Concurrent命名空间。

于 2013-01-14T15:53:34.393 回答
2

List<T>通常不是线程安全的。拥有多个阅读器不会导致任何问题,但是,您不能在读取列表时写入列表。因此,您需要同时锁定读取和写入,或使用 System.Threading.ReaderWriterLock 之类的东西(它允许多个读取器,但只允许一个写入器)。如果你在 下开发.NET 4.0 or bigger,你可以使用BlockingCollection来代替,它是一个线程安全的集合。

于 2013-01-14T15:57:05.737 回答
0

不,那不安全。如果另一个线程在您阅读它时修改它,您将收到“集合已修改”类型的异常。

解决此问题的最有效方法是使用ReaderWriterLockSlim来控制访问,以便多个线程可以同时读取它,并且只有在尝试修改它时才会被锁定。

于 2013-01-14T15:55:27.810 回答
0

如果你从不迭代它,你甚至不是线程安全的

在我们讨论它是否会按预期工作之前,您需要定义您对数据结构执行的操作类型。

但在一般情况下,您确实需要在阅读时锁定。事实上,有人可能会在您进行迭代时添加一个项目,这会破坏各种事情。如果您在阅读过程中添加了一个项目,即使阅读单个项目也可能会被破坏。

另请注意,这充其量只会使每个操作在逻辑上原子化。如果您曾经执行多个操作并对数据结构的状态做出假设,那么这还不够。

在许多情况下,要解决此问题,您需要在调用方进行锁定,而不是仅将每个操作包装在lock.

于 2013-01-14T15:55:48.917 回答
0

您可能应该使用 aReaderWriterLockSlim以便多个线程可以读取集合,但只有一个可以修改它。

于 2013-01-14T15:56:06.477 回答
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();

迭代也必须锁定!!!

于 2013-01-14T16:07:41.093 回答
-1

您可能想看看 ConcurrentQueue<>();

这基本上是一个线程安全列表(据我所知),这很方便。你可以像这样使用它;

   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

于 2013-01-14T16:00:35.533 回答