【问题标题】:Does variable value change which is used inside Lock statement c#?在Lock语句c#中使用的变量值是否会改变?
【发布时间】:2013-03-18 06:22:14
【问题描述】:
   public Response Delete(Request fRequest)
        {
            Response fResponse = new Response();
            try
            {

                    foreach (Record rec in fRequest.Records)
                    {
                        string personid = rec.PersonId;
                        int recordId = rec.RecordId;

                        EnrollRecord record = (from lst in listEnrolledCandidates
                                            where lst.PersonId == personid && lst.RecordId      == recordId
                                            select lst).FirstOrDefault();
                      lock (lockDelete)
                      {
                        listEnrolledCandidates.Remove(record);

                        int aIndex = record.ArraryIndex;  
                        pvTemplatesArrayList.RemoveAt(aIndex)                       

                      }
                    }

                    return fResponse;

            }
            catch (Exception ex)
            {


                return null;
            }

        }

在上面的源代码中,锁是在 Remove 语句上保持的。这意味着一次只有一个线程可以执行这三行代码。现在,如果一个线程(例如“A”)正在执行这 3 行,而另一个线程(例如“B”)使用另一个 Request 参数调用“Delete”方法,线程 A 是否有可能在执行时获取 EnrollRecord、personid 和recordid 的更新值Request 对象的操作不同?

【问题讨论】:

  • 这可能会有意想不到的行为,为什么锁里面也没有查询enrollrecord的语句。
  • 我同意,假设 listEnrolledCandidates 是一个数组列表(从标签推断),那么如果 2 个线程请求删除相同的记录并且都同时获得锁,第一个删除记录和 pvTemplatesArrayList 中的项目,然后下一个不会删除记录(因为如果项目不在 arraylist 中,arraylist.remove 不会做任何事情)但是 pvTemplatesArrayList 将删除数组索引处的项目。我认为会有很多这样的竞争条件。
  • Matt,请提出解决此类问题的方法。
  • 请参阅我的建议作为答案。
  • 不幸的是,您的问题中缺少 lockDelete 变量的声明。因此无法预测行为将如何。请更新您的代码以包含此信息。

标签: c# multithreading arraylist


【解决方案1】:

我认为 'personId' 和 'recordId' 的值不会根据每个线程的执行而改变(假设 fRequest 参数对每个线程都是唯一的)

但是,“记录”可能会发生奇怪的事情。没有改变值,但是线程 A 获取了它的值,然后线程 B 从列表中删除了该值,因此它实际上不再存在。

任何“全局”或共享变量都需要在多线程情况下对其访问进行同步。

我会将列表访问权限移到锁内。

【讨论】:

    【解决方案2】:

    是的,这似乎是完全可能的,所以EnrollRecord record = (from lst in listEnrolledCandidates where lst.PersonId == personid && lst.RecordId == recordId select lst).FirstOrDefault(); 必须是临界区的一部分,并受到多线程的保护。

    【讨论】:

    • 但是“EnrollRecord 记录”将被视为线程 A 上下文中的本地对象。A 将包含它自己的副本,而线程 B 将包含它自己的副本。不是吗?
    • listEnrolledCandidates 是全局而非 EnrollRecord
    【解决方案3】:
    • 不,线程 B 不会更改线程 A 的 recordid andpersonid 值`
    • 不,线程 B 不会将线程 A 的 引用 更改为 record,但线程 B 可以更新 EnrollRecord 对象的内部状态(如果它们都引用)相同的EnrollRecord 对象。
    • 不过,最重要的是,您的方法可能不是线程安全的:

    编辑:

    我不允许发表评论,因此请在您的其他评论中明确您的问题:

    但是“EnrollRecord 记录”将被视为线程 A 上下文中的本地对象。A 将包含它自己的副本,线程 B 将包含它自己的副本。难道不是

    这可能并非如此,理解这一点很重要。我认为EnrollRecord 是一个类。因此它是一个引用类型。每个线程都可以通过record引用同一个对象,但是它的引用变量record在线程之间是不共享的。正如您的代码所代表的那样,每个线程都不会创建EnrollRecord 的不同副本。例外情况是,例如,您的 linq 查询导致发生 sql 查询,并且您的数据提供者允许在内存中创建对象的重复副本。我假设情况并非如此,因为您使用锁来保护您的删除,建议listEnrolledCandidates 使用一个常见的内存集合。

    END EDIT,继续原来的答案:

    为了使该方法线程安全,您可能还需要使用锁保护以下行:

    EnrollRecord record = (from lst in listEnrolledCandidates
                                            where lst.PersonId == personid && lst.RecordId      == recordId
                                            select lst).FirstOrDefault();
    

    为什么?

    我将尝试将您的问题分解为各个部分,并逐一回答:

    1. 一次只有一个线程可以执行锁内的 3 行
    2. 线程 A 是否有可能在执行操作时获取以下更新值,因为请求 (fRequest) 对象不同:
      • EnrollRecord
      • personid
      • recordid

    因此,我们将从您的陈述中假设“作为请求 (fRequest) 对象不同”,您的意思是:请求 (fRequest) 对象是单独创建的,因此它不是可能的引用线程 A 和 B 之间共享。

    把这些放在一起:

    一次只有一个线程执行锁块内的 3 行。

    我知道这不是一个问题,但首先澄清这一点很重要,因为这有助于理解为什么线程 A 上的 personidrecordId 不会被线程 B 更新。

    任何数量的线程都可以进入一个方法,但您需要记住该方法访问的对象的范围。您几乎可以将编写的方法描绘为“蓝图”,当线程进入一个方法时,它会从该蓝图创建一个物理构造。每个线程都有自己的物理构造。将蓝图视为汽车的设计。你和我可能驾驶相同品牌、型号、年份、颜色的汽车,但我开车时你不会坐在我的腿上,当你更换电台时,我的电台不会改变——我们每个人都有我们自己的副本。

    在你的例子中:

    1. string personid 的作用域是方法,因此它的值特定于调用此方法的线程。
    2. 同样适用于int recordId
    3. EnrollRecord record 也适用于该方法,因此您在线程 A 中引用的 record 不能由线程 B 中的 record 更新。

    不过,recordreference type,而 intvalue type。这个非常重要。 record 是一个对象的引用。它被限定在一个方法内。因此,每个线程可能持有对同一对象或不同对象的引用。如果一个线程更改了它的引用,它不会影响另一个线程,但如果他们更改了引用对象中的某些内容(请参阅下面的其他点),那么两个线程都会看到更改。

    如果其中任何一个是类字段而不是方法变量,那么一个线程可以更改值/引用另一个线程看到的。

    引用类型与值类型

    值类型:如果线程 A 更新变量 recordId,线程 B 将看不到这个。如果 recordId 被声明为类级别的字段(例如 private int recordId)而不是方法级别的变量,那么情况就不会如此 - 任何线程都可以访问该值并且可以阅读和更新它。要理解为什么会这样,您需要研究堆栈和堆存储之间以及变量范围的区别。 带回家:在方法中声明和使用的值类型(大多数情况下)不能被运行相同方法的其他线程访问。

    参考类型 - 如果线程A将值record设置为(1, 1)为(PersonId,RecordId)的记录,然后线程B将其设置为对应于(2, 2)的记录,则线程A仍将参考 (1, 1)。 - 如果线程A将值record设置为(1, 1)的(PersonId,RecordId)的记录,然后线程B将其设置为对应于(1, 1)的记录,那么线程A仍将引用 (1, 1),线程 B 也是如此。

    所以,是的,一次只有一个线程可以进入锁定区域,但您不需要锁定来保护本地声明的变量。需要锁来保护共享状态,而不是线程本地的变量。如果不是这种情况,我们将无法从多个线程运行以下方法并获得正确答案:

    public int MinusOne(int value) { 
        int newValue = value - 1; 
        return newValue; 
    }
    

    你的问题最重要的一点

    问题中最重要的部分是您实际上没有问的问题。问题是: 这个方法是线程安全的

    当我看到您的代码时,答案必须是:

    为了回答您的问题,我必须假设listEnrolledCandidates 是对IEnumerable<EnrollRecord> 类型列表的共享引用。我还必须假设,因为您正在使用锁定从该列表中删除一条记录,所以它不是线程安全的集合/列表/字典。

    这很关键: 您正在锁定,因此线程 B 无法尝试从 listEnrolledCandidates 删除记录,而线程 A 会这样做,但在以下情况下会发生什么情况:

    场景 1。

    • 线程 A 在其范围内设置对 record 的引用
    • 线程 B 开始枚举listEnrolledCandidates 以查找记录
    • 线程A删除记录
    • 线程 B 完成枚举

    场景 2。

    • 线程A设置对record的引用,对应搜索键(1, 1)
    • 线程B设置对record的引用对应搜索键(1, 1)
    • 线程 A 现在删除记录
    • 线程 B 现在删除记录

    在这两种情况下,您最终都可能会遇到问题。如果您使用的是非线程安全的集合、列表、字典,则必须保护搜索、枚举、循环以及添加、删除、更新等。出于这些目的,您可以积极使用lock,也可以使用ReaderWriterLock(或相同的精简版)。

    在第二种情况下,您还想在进入删除锁定区域时再次检查记录是否仍然存在,以防 Remove 引发 record 不再在集合中的异常。

    其他要点

    假设您在EnrollRecord 上有一个属性,例如public string EnrollYear { get; set; }。还假设线程 A 和线程 B 都引用 (1, 1) 的 (PersonId, RecordId)。 - 如果线程 A 将 record 上的 EnrollYear 更改为新值,则线程 B 将看到此更改。 - 如果线程 A 将 record 设置为 null,那么线程 B 将不会看到记录为 null,它仍然会看到保持其对记录的引用

    在我们的汽车示例中:如果您调用我正在收听的广播电台(假设 RadioStation 是一个具有 CurrentSong 属性的对象)并请求一首新歌曲,您将更改我正在收听的歌曲。 (我想我已经尽可能地采用了这个类比)。

    在这个例子中,每个线程都有一个对相同对象的单独引用

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-06-27
      • 2023-03-28
      • 2021-11-04
      • 1970-01-01
      • 2022-09-27
      • 2013-01-12
      • 1970-01-01
      相关资源
      最近更新 更多