Interlocked.CompareExchange <t>?</t>是否使C#“lock”构造过时

时间:2009-09-15 23:52:00

标签: deadlock lock-free interlocked livelock

6 个答案:

答案 0 :(得分:3)

你的比较交换建议有一个很大的缺点 - 它不公平,因为它有利于短期任务。如果系统中有许多短任务,那么长期任务完成的可能性可能会非常低。

答案 1 :(得分:2)

比赛有四个条件。

  1. 第一个条件是存在可从多个线程访问的内存位置。通常,这些位置是全局/静态变量,或者是可从全局/静态变量访问的堆内存。
  2. 第二个条件是存在一个属性(通常称为不变量),它与这些共享内存位置相关联,这些位置必须为true或有效,才能使程序正常运行。通常,在更新发生更新之前,属性需要保持为正确。
  3. 第三个条件是在实际更新的某些部分期间不变属性不成立。 (在处理的某些部分,它暂时无效或错误)。
  4. 竞争发生必须发生的第四个也是最后一个条件是另一个线程在不变量被破坏时访问内存,从而导致行为不一致或不正确。

    1. 如果您没有可从多个线程访问的共享内存位置,或者您可以编写代码以消除该共享内存变量,或将访问限制为仅限一个线程,那么没有竞争条件的可能性,你不需要担心任何事情。否则,锁定语句或其他一些同步例程是绝对必要的,不能安全地忽略。

    2. 如果没有不变量(假设您所做的只是写入此共享内存位置,并且线程操作中没有任何内容读取它的值)那么再次,没有问题。

    3. 如果不变量永远无效,那么也没问题。 (假设共享内存是一个日期时间字段,存储上次代码运行的日期时间,那么除非线程根本无法写入它,否则它不能无效...

    4. 要消除nbr 4,您必须使用锁或某种类似的同步方法限制对一次访问多个线程的共享内存的代码块的写访问权限。

  5. 在这种情况下,“并发命中”不仅是不可避免的,而且是绝对必要的。智能分析共享内存究竟是什么,以及您的关键“不变”究竟是什么允许您对系统进行编码以最小化并发“命中”。 (即最大化并发性安全。)

答案 2 :(得分:1)

我想知道如何使用无锁编程风格执行此任务?您有许多工作线程都会定期访问共享的任务列表,以便执行下一个作业。 (当前)他们锁定列表,找到头部的项目,删除它,并解锁列表。请考虑所有错误情况和可能的数据争用,以便没有两个线程可以最终处理同一任务,或者意外跳过任务。

我怀疑执行此操作的代码可能会遇到过度复杂的问题,并且在高争用的情况下可能会出现性能不佳。

答案 3 :(得分:1)

锁定CAS操作(如Interlocked.CompareExchange)的一大优势是,您可以修改锁定中的多个内存位置,并且所有修改将同时对其他线程/进程可见。

使用CAS,只有一个变量以原子方式更新。无锁代码通常要复杂得多,因为您不仅可以一次向其他线程提供单个变量(或两个相邻变量与CAS2)的更新,还必须能够处理CAS失败时的“失败”条件没成功。此外,您需要处理ABA问题和其他可能的并发症。

有各种方法,如低锁定,细粒度锁定,条纹锁定,读写器锁定等,可以使简单的锁定代码更加多核友好。

也就是说,锁定和无锁代码都有很多有趣的用途。但是,除非你真的知道你正在做什么,否则创建你自己的无锁代码不适合初学者。使用已经过充分验证的无锁代码或算法并对其进行彻底测试,因为很难找到导致许多无锁尝试失败的边缘条件。

答案 4 :(得分:1)

我想说,一般来说,悲观的并发性在乐观并发中已经过时,或者由于模式B而模式A已经过时,这并不过时。我认为它是关于上下文的。无锁是强大的,但单方面应用它可能没有意义,因为不是每个问题都非常适合这一点。有权衡。也就是说,如果传统上没有实现通用无锁,乐观的方法,那将是一件好事。简而言之,是的,锁定 可以做一些用其他方法无法实现的事情:代表一种可能更简单的解决方案。然后,如果某些事情无关紧要,可能两者都有相同的结果。我想我有点暧昧......

答案 5 :(得分:0)

理论上,如果要完成一定数量的工作,使用Interlocked.CompareExchange的程序将无需锁定即可完成所有工作。不幸的是,在存在高争用的情况下,read / compute-new / compareExchange循环最终可能会严重颠覆,每个尝试对公共数据项执行一次更新的100个处理器最终可能需要更长的时间 - 在 real中时间 - 比单个处理器按顺序执行100次更新。并行性不会提高性能 - 它会杀死它。使用锁来保护资源意味着一次只有一个CPU可以更新它,但会提高性能以匹配单CPU情况。

无锁编程的一个真正优势是,如果一个线程在任意时间内被运行,系统功能将不会受到不利影响。通过使用锁和超时的组合,可以保持这种优势,同时避免纯粹基于CompareExchange的编程的性能缺陷。基本思想是,在存在争用的情况下,资源切换到基于锁的同步,但如果线程持有锁太长时间,则将创建新的锁对象,并且将忽略先前的锁。这意味着如果前一个线程仍在尝试执行CompareExchange周期,它将失败(并且必须重新开始),但后来的线程不会被阻塞,也不会牺牲正确性。

请注意,仲裁上述所有内容所需的代码将是复杂且棘手的,但如果想要在某些故障情况下系统具有健壮性,则可能需要此类代码。