繁体   English   中英

多线程概念和C#中的锁定

[英]Multi-threading concept and lock in c#

我读到有关锁的内容,尽管一点也不了解。 我的问题是,为什么我们要使用未使用的object并将其锁定,以及这如何使某些线程安全或如何在多线程中发挥作用? 没有其他方法可以创建线程安全的代码。

public class test {
    private object Lock { get; set; }

    ...
    lock (this.Lock) { ... }
    ...
}

抱歉,我的问题很愚蠢,但是我听不懂,尽管我已经使用了很多次。

在一个线程正在修改另一个线程的同时从一个线程访问数据的过程称为“数据竞争条件”(或简称为“数据竞争”),并且可能导致数据损坏。 (*)

锁只是避免数据争用的一种机制。 如果两个(或多个)并发线程锁定同一个锁对象 ,则在锁定期间 ,它们不再并发并且不再导致数据争用。 本质上,我们正在序列化对共享数据的访问。

诀窍是使锁尽可能保持“宽”,以避免数据争用, 而又保持“窄”,以便通过并发执行获得性能。 这是一个很好的平衡点,可以很容易地在任何一个方向突破,这就是为什么多线程编程很难实现的原因。

一些准则:

  • 只要所有线程都在读取数据,而且任何线程都不会修改它,就不需要进行锁定。
  • 相反,如果至少一个线程可能在某个时候修改数据,那么访问同一数据的所有并发代码路径必须通过锁正确地序列化,即使那些仅读取数据的锁也是如此。
    • 在一个代码路径中使用锁而不在另一个代码路径中使用锁将使数据在竞争条件下保持开放状态。
    • 此外,使用一个代码路径一个锁定的对象,但在另一个(并发)代码路径不同的锁对象不序列这些代码路径,并留下您全方位开放的数据竞争。
    • 另一方面,如果两个并发代码路径访问不同的数据,则它们可以使用不同的锁对象。 但是 ,只要有一个以上的锁定对象,请当心死锁 死锁通常也是“代码竞争条件”(还有heisenbug,请参见下文)。
  • 锁定对象不必与(通常不是)您要保护的数据相同。 不幸的是,没有语言工具可以让您“声明”哪个数据受哪个锁定对象保护,因此您必须非常仔细地为可能维护您代码的其他人和您自己记录“锁定约定”(因为即使经过很短的时间,您也会忘记锁定约定的某些细节。
  • 通常,尽可能多地保护锁对象不受外界干扰是一个好主意。 毕竟,您将其用于非常敏感的锁定任务,并且您不希望外部参与者以无法预料的方式锁定它。 这就是为什么使用this字段或公共字段作为锁定对象通常不是一个好主意的原因。
  • lock关键字只是Monitor.EnterMonitor.Exit的一种更方便的语法。
  • 锁定对象可以是.NET中的任何对象, 但是 值对象将在Monitor.Enter的调用中放入 ,这意味着线程将不会共享同一锁定对象,从而使数据不受保护。 因此,仅将引用类型用作锁定对象。
  • 对于进程间通信,您可以使用全局互斥锁,可以通过将非空name传递给Mutex Constructor来创建全局互斥 全局互斥锁提供与常规“本地”锁定基本相同的功能,不同之处在于它们可以在单独的进程之间共享。
  • 除锁外,还有其他同步机制,例如信号量,条件变量,消息队列或原子操作 混合使用不同的同步机制时要小心。
  • 锁还充当内存屏障 ,这在现代多核,多缓存CPU中变得越来越重要。 这是您需要锁定读取数据而不仅仅是写入的原因的一部分

(*) 之所以称为“竞赛”,是因为并发线程正在“竞赛”走向对共享数据执行操作,而赢得比赛的人将决定操作的结果。 所以结果取决于执行力,这是对现代抢占式多任务操作系统基本上是随机的时机 更糟糕的是,通过使用诸如调试器之类的工具来观察程序执行的简单动作,就很容易修改定时,这使它们成为“ heisenbugs” (即,仅通过观察即可改变所观察到的现象)。

lock语句引入了互斥的概念。 任何时候只有一个线程可以获取给定对象的锁。 这样可以防止线程同时访问共享数据结构,从而破坏它们。

如果其他线程已经持有锁,则lock语句将阻塞,直到能够对其参数获取排他锁,然后才允许其阻塞执行。

请注意, lock唯一要做的就是控制代码块的输入。 对类成员的访问与锁完全无关。 由类本身来确保使用lock或其他同步原语来协调必须同步的访问。 另请注意,对某些或所有成员的访问可能不必同步。 例如,如果您想要维护一个计数器,则可以使用Interlocked类而不进行锁定。


锁定的替代方法是无锁数据结构,该结构在存在多个线程的情况下可以正确运行。 通常必须借助诸如比较交换(CAS)之类的无锁原语来非常仔细地设计无锁数据结构上的操作。

这种技术的一般主题是尝试对数据结构执行原子操作,并检测何时由于其他线程的并发操作而导致操作失败,然后重试。 这在不太可能发生故障的轻负载系统上效果很好,但是随着故障率的上升和重试成为主要负载,可能会导致失控行为。 通过降低重试率,有效地限制负载,可以缓解此问题。


一个更复杂的替代方法是软件事务存储。 与CAS不同,STM将失败和重试的概念推广到任意复杂的内存操作。 简而言之,您可以启动事务,执行所有操作并最终提交。 系统检测是否由于将当前线程打到打孔机的其他线程执行的冲突操作而无法成功执行操作。 在这种情况下,STM可能会彻底失败,要求应用程序采取纠正措施,或者在更复杂的实现中,它可以自动返回到事务开始并重试。

当您有不同的线程同时访问相同的变量/资源时,它们可能会对此变量/资源进行覆盖,并且您可能会得到意想不到的结果。 锁定将确保只有一个线程可以按时评估变量,而其余线程将排队等待访问该变量/资源,直到释放锁定为止

假设我们有一个帐户的余额变量。 两个不同的线程读取了其值为100的值。假设第一个线程向其添加了50,如100 + 50并保存了该值,则余额将为150。由于第二个线程已读取了100并表示同时。 假设它像100-50一样减去50,但要注意的是,第一个线程使天平达到150,因此第二个线程应达到150-50,这可能会导致严重问题。

因此,锁定可确保在线程上要更改某些资源状态时,它将锁定它并在提交更改后离开

是的,确实存在另一种方式:

using System.Runtime.CompilerServices;

class Test
{
    private object Lock { get; set; }

    [MethodImpl(MethodImplOptions.Synchronized)]
    public void Foo()
    {
        // Now this instance is locked
    }
}

尽管看起来更“自然”,但它并不经常使用,因为该对象以这种方式锁定自身 ,因此其他代码也不会冒着对该对象锁定的风险-可能会导致死锁。

因此,您通常创建一个(延迟初始化的)私有字段来引用一个对象,然后将该对象用作锁。 这样可以确保没有其他人可以锁定与您相同的对象。


有关幕后情况的更多详细信息:

当您“锁定对象”时,您并不是在锁定对象本身 而是,您将对象用作整个程序中保证唯一的内存地址。 当您“锁定”时,运行时将获取对象的地址,使用它来查找另一个表(对您而言是隐藏的)内的实际锁,并将对象用作““锁”(也称为“关键段”) ”)。

所以实际上,对您来说,对象只是一个代理/符号-它本身并不做任何事情; 它只是作为唯一的指示器,永远不会与同一程序中的另一个有效对象发生冲突。

Lock对象就像是进入单人房的一扇门 ,每次只能有一位客人进入。 房间可以作为您的数据 ,客人可以作为您的职能

  • 定义数据(房间)
  • 添加门(锁定对象)
  • 邀请来宾(功能)
  • 使用lock指令关闭/打开门,每次仅允许一名客人进入房间。

为什么我们需要这个? 如果您模拟地在文件中写入数据(例如,其他人可能要成千上万),则需要将对真菌的访问(来宾的关门/打开门)同步到写入文件,因此任何功能都将附加到末尾的文件(假设这是此示例的要求)

自然,这不仅是同步线程的方式,还有更多的方式:

  • 监控器
  • 等待哈德勒...

查看链接以获取完整的信息和每个描述

线程同步

对于那些刚刚熟悉C#中的lock关键字的人来说,您的困惑是非常典型的。 没错, lock语句中使用的对象实际上只是定义关键部分的令牌而已。 该对象绝不会对多线程访问本身有任何保护。

这种工作方式是CLR在称为同步块的对象标头(类型句柄)中保留4字节(32位系统)节。 同步块不过是存储实际关键部分信息的数组索引。 当您使用lock关键字时,CLR将相应地修改此同步块值。

该方案具有优点和缺点。 优点是,它为定义关键部分提供了一种非常优雅的解决方案。 一个明显的缺点是,每个对象实例都包含同步块,并且大多数实例从不使用它,因此在大多数情况下,这似乎是在浪费空间。 另一个缺点是可以使用装箱的值类型,这几乎总是错误的,并且肯定会引起混淆。

我记得当.NET首次发布时,人们一直在争论lock关键字对于语言是好是坏。 普遍的共识(至少在我记忆中是这样)是不好的,因为using关键字可以很容易地被使用。 实际上,使用using关键字的解决方案实际上更有意义,因为它可以完成而无需sync块。 C#设计团队甚至记录说,如果给他们第二次机会, lock关键字将永远不会进入语言。 1个


1 我能找到这样做的唯一的参考是乔恩斯基特的网站在这里

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM