【问题标题】:Is this algorithm thread safe?这个算法线程安全吗?
【发布时间】:2011-07-20 19:02:30
【问题描述】:

我想知道下面的类是否线程安全并且在多线程上下文中完美运行?

public class ThreadSafeClass
{
    private List<int> commandList = new List<int>();
    public void AddCommand(int newCommand)
    {
        lock(syncObj) {
          commandList.Add(newCommand);
        }
    }
    public List<int> Split()
    {
        List<int> oldList = commandList;
        commandList = new List<int>();
        return oldList;
    }
}

ThreadA周期性调用split方法,很多线程调用AddCommand,split方法中的commandList指向内存中的某个list,分配新list时所有内容都在oldList中

拆分的目的是我想获取所有排队的命令,并在下一次调用中返回其余的命令,...同时让应用程序向 commandList 添加新项目。

因为我专注于拆分方法,我忘记为添加操作添加锁,因为它不是线程安全的谢谢:(可以 poyrazoğlu)但问题仍然存在于拆分

【问题讨论】:

  • Split() 应该做什么?您可以将文档添加到您的类及其成员函数中吗?
  • 根据您的问题,您不应该编写多线程代码,除非作为学习实验。这是一个血统介绍:msdn.microsoft.com/en-us/library/aa446522.aspx 您还可以在 System.Threading 命名空间中做一些阅读。这篇文章列出了一些你应该学习的原语:csharptest.net/?p=323

标签: c# .net multithreading thread-safety


【解决方案1】:

代码不是线程安全的,因为 List.Add 不能保证是。 (只是第一件事 - 还有其他问题)。

您也没有锁定共享数据(对commandList 的任何访问)。

【讨论】:

  • +1,这就是我试图确认但在 MSDN 中找不到答案。
  • @Yuck:“不保证任何实例成员都是线程安全的。” source
  • List.Add 不是线程安全的并不重要,因为 oldList 在 split 方法中引用了它,如果在创建新列表之间发生任何添加操作,最终添加操作将其项目添加到一个oldList 或 newsList 我只是想确保没有丢失任何项目,你能给我一个它失败的序列
  • @Ehsan - commandList 在线程A 中分配给oldList,线程B 现在调用AddCommand,完成。 threadA 恢复执行,你丢失了添加的命令。
  • @Ehsan - 你应该看看System.Collections.Concurrent 命名空间中现有的线程安全集合。
【解决方案2】:

这里的问题归结为您期望的行为。以你的 split 方法为例:

public List<int> Split()
{
    List<int> oldList = commandList;
    commandList = new List<int>();
    return oldList;
}

在分配oldList 和重新分配commandList 之间有一段时间,AddCommand 方法可以将值添加到commandList,这将出现在oldList 中:

public List<int> Split()
{
    // Say commandList contains 1 and 2

    List<int> oldList = commandList;

    // Now, on another thread, this happens:
    //
    // AddCommand 3
    // AddCommand 4
    // AddCommand 5
    //
    // The list hasn't been reassigned yet, so at this point oldList and
    // commandList both have 1, 2, 3, 4, and 5.

    commandList = new List<int>();

    // Now, commandList is empty, and oldList contains 1, 2, 3, 4, and 5,
    // even though it only contained 1 and 2 when you first assigned it.

    return oldList;
}

此序列表明oldList 不是仅包含其分配时的值的快照,而是实际上可以在分配它的时间和重新分配commandList 的时间之间进行修改。

这段代码的真实情况是,您添加的每个命令都将位于oldList 或commandList 恰好一次。无论有多少线程调用AddCommand,您都不会遇到任何重复。这听起来像是你想要完成的,所以我认为你的代码是正确的。

这是因为.NET reference assignment is atomic。在分配commandList 期间,没有任何时间段调用AddCommand 会导致值被添加到多个列表中或根本不被添加。

如果我误解了您的问题,请告诉我。

【讨论】:

  • 这正是我想知道的。
【解决方案3】:

拥有线程安全的类并不意味着你的程序是线程安全的。线程安全类只是意味着您可以从多个线程中使用它,它仍然可以。线程安全基本上意味着“在多线程环境中保持一致”。

List 类不是线程安全的,因此您的代码绝对不是线程安全的。但想法是,即使你使用线程安全的集合,也并不意味着你的代码是线程安全的。

【讨论】:

    【解决方案4】:

    没有。 想想一个线程调用AddCommand 而另一个线程调用Split 的情况。您的新命令可能会被清除。您需要使用锁定机制来同步它。

    // Bad
    List oldList = commandList;
    commandList.Add(newCommand); // newCommand would get lost.
    commandList = new List();
    return oldList; 
    

    【讨论】:

    • 不,因为它根本不在新的commandList 中。 oldList 仍然有对 List 的引用,所以 newCommand 在那个引用中。
    • 旧列表中没有新命令,这是可以接受的。我想确保在 newList 或 oldList 中没有丢失任何项目
    【解决方案5】:

    我不认为这是线程安全的。你修改了共享数据,我看不到任何守卫。

    【讨论】:

    • 你能给我一个失败的序列吗?
    • 当然 - 一个线程调用 AddCommand 而另一个线程调用 Split。发生什么了?它是确定性的吗?
    【解决方案6】:

    没有。

    如果一个线程开始“添加”操作,而另一个线程正在执行split,则存在并发问题(提示,虽然add 的代码只有一行,但它是计算机本身的许多操作)

    【讨论】:

      【解决方案7】:

      不,代码不是线程安全的。默认情况下,类不是线程安全的。

      这是一个链接Why is List<T> not thread-safe?

      【讨论】:

        【解决方案8】:

        List&lt;T&gt; 类不是线程安全的。使用 SynchronizedCollection&lt;T&gt;,其中 Add、Remove 等方法是线程安全的(枚举仍然不是,所以不要使用 foreach)

        【讨论】:

          【解决方案9】:

          您应该通过锁定一个对象来同步对列表的访问(在这种情况下,它应该是所有方法的同一个对象),其中使用 commandList 本身是安全的,因为它不会重新创建或任何东西。

              public class ThreadSafeClass
              {
                  private object sync = new object();
                  private List<int> commandList = new List<int>();
                  public void AddCommand(int newCommand)
                  {
                    lock(sync){
                      commandList.Add(newCommand);
                    }
                  }
                  public List<int> Split()
                  {
                    lock(sync){
                      List<int> oldList = commandList;
                      commandList = new List<int>();
                    }
                      return oldList;
                  }
              }
          

          【讨论】:

          • 是的,我不想支付锁定开销,并且我相信我的课程工作正常,因为新添加的项目位于 (oldList, commandList) 之一中,这是可以接受的。但丢失一个项目不是,所以如果你给我看一个它丢失一个项目的序列,将不胜感激。
          • 如果(没有锁定)两个线程调用 AddCommand 怎么办? List 类本身不是线程安全的(除非您只是在阅读不是这种情况),因此您要么锁定,要么从头开始实现自己的列表。
          • 那么如果两个线程调用 AddCommand 会发生什么,假设其中一个完成但另一个未完成,并且在第三个线程中我调用 split。实际添加到命令列表中的所有内容都将返回,如果它处于添加操作的中间,它将排队等待新闻拆分命令
          • 然后一切都搞砸了 :) 顺便说一句,我没有意识到您在拆分时分配了一个新列表,我的大脑将其解析为 Clear(),这样您就无法锁定 commandList ,所以我创建了一个仅用于同步的对象。对于你的问题,忘记第三个线程,你不能在假设上编码,你必须考虑每一种情况。如果有两个线程试图异步写入(例如添加)同一个列表,那么它们有时肯定会失败。可能有也可能没有第三个线程,没关系。
          • 所以如果问题是添加操作,我们可以锁定添加操作,拆分的原因是什么?
          【解决方案10】:

          您的班级在这里的形式似乎揭示了解决经典生产者-消费者问题变体的愿望:

          http://en.wikipedia.org/wiki/Producer-consumer_problem

          我可能误解了你的愿望。首先,正如许多其他人所提到的,您的代码中没有锁定机制,因此,这两种方法之间存在固有的竞态条件。如果您没有立即理解“竞争条件”一词的含义,那么您不应该编写多线程代码,请阅读书籍。不想在这里粗鲁,但我分享Alex's sentiments。

          问题真的变成了:你想在这个类的多线程场景中做什么?这个问题的答案将引导您找到适合您应用程序的同步机制。

          对于以下两个示例,让我们假设一个简单的互斥锁,就像其他人已经发布的那样。

          1. 如果有 100 个线程都试图同时调用 AddCommand(...) 方法怎么办?其中 99 个会阻塞,尤其是在底层列表很大并且正在重新分配的情况下。可以吗?那是你要的吗?这在您正在开发的应用程序中是不可能的吗?

          2. 在 100 个线程的情况下,其中只有一个线程会调用 Split(),而另外 99 个线程会调用 AddCommand(...),调用 Split() 的线程将获得 99 个命令列表的子集,而不是全部,因为一个简单的锁定结构不会调用 Split() 阻塞,直到 all 挂起的 AddCommand(...) 调用完成,这将(从数据处理的角度来看)更理想。

          现在,更复杂的锁定机制可能会解决这些问题;但是,这些问题可能在您的应用程序中不存在,因此不需要解决,一个简单的锁定机制就足够了。

          希望这可以帮助您找到正确的方向。

          【讨论】:

          • commandList 位于一个定期调用拆分方法的线程中,因此如果返回的命令列表包含所有 100 项或更少的项目,则它并不重要,因为在下一次调用中,新项目出现在列表中,我没有不想在这里粗鲁,但我的大多数朋友不明白代码的目的是什么,我想知道的一切都在 split 方法中,我想确保这个方法不会丢失任何项目(threadA 定期调用split 方法,很多线程调用 AddCommand) AddCommand 需要锁但是为什么 split 需要锁?
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2012-02-17
          相关资源
          最近更新 更多