《CLR via C#》笔记:第5部分 线程处理(3)(完结)

  1.7 C#, 3.2 游戏引擎技术
  • 本博客所总结书籍为《CLR via C#(第4版)》清华大学出版社,2021年11月第11次印刷(如果是旧版书籍或者pdf可能会出现书页对不上的情况)
  • 你可以理解为本博客为该书的精简子集,给正在学习中的人提供一个“glance”,以及对于部分专业术语或知识点给出解释/博客链接。
  • 【本博客有如下定义“Px x”,第一个代表书中的页数,第二个代表大致内容从本页第几段开始。(如果有last+x代表倒数第几段,last代表最后一段)】
  • 电子书可以在博客首页的文档-资源归档中找到,或者点击:传送门自行查找。如有能力请支持正版。(很推荐放在竖屏上阅读本电子书,这多是一件美事)
  • 欢迎加群学习交流:637959304 进群密码:(CSGO的拆包密码) 

  • 本人对于线程的经验仅限于开辟线程以及线程同步方面的简易操作。对于深入工程应用实践仍有缺乏,故本大部分的笔记补充能容可能会在未来半年-一年内陆续进行。

第二十九章 基元线程同步构造

  • 线程同步问题:1、比较繁琐,代码容易写错。2、损害性能。有锁至少会数倍慢与无锁。3、一次只允许访问一个资源。(P659 3)
  • 如何尽可能避免线程同步:像静态字段一样共享数据,尝试使用值类型,多个线程同时对共享数据进行只读访问没有任何问题。(P670 last2)

类库和线程安全

  • 类库和线程同步:Microsoft的Framework Class Library(FCL)保证所有静态方法都是线程安全的。这意味着假如两个线程同时调用一个静态方法,不会发生数据被破坏的情况。另一方面,FCL不保证实例方法是线程安全的,因为假如全部添加锁定,会造成性能的巨大损失。(P671 last2)

基元用户模式和内核模式构造

  • 基元线程同步构造。基元(primitive)是指可以在代码中使用的最简单的构造。有两种基元构造:用户模式(user-mode)和内核模式(kernel-mode)。应尽量使用基元用户模式构造,它们的速度要显著快于内核模式的构造。(P672 1)
  • 对于在一个构造上等待的线程,如果拥有这个构造的线程一直不释放它,前者就可能一直阻塞。如果是用户模式的构造,线程将一直在一个CPU上运行,我们称为“活锁”(livelock)。如果是内核模式的构造,线程将一直阻塞,我们称为“死锁”(deadlock)。两种情况都不好。但在两者之间,死锁总是优于活锁,因为活锁既浪费CPU时间,又浪费内存(线程栈等),而死锁只浪费内存。(P572 last2)

用户模式构造

  • CLR保证对以下数据类型的变量的读写是原子性的:Boolean,Char,(S)Byte,(U)Int16,(U)Int32,(U)IntPtr,Single 以及引用类型。这意味着变量中的所有字节都一次性读取或写入。(P673 2)
  • 两种基元用户模式线程同步构造:(P673 last)
    1、易变构造(volatile construct):在特定的时间,它在包含一个简单数据类型的变量上执行原子性的读或写操作。
    2、互锁构造(interlocked construct):在特定的时间,它在包含一个简单数据类型的变量上执行原子性的读和写操作。
  • 禁止各种优化,以避免同步问题的出现:(P676 last2)
    1、Volatile.Write方法强迫location中的值在调用时写入。此外,按照编码顺序,之前的加载和存储操作必须在调用Volatile.Write之前发生。
    2、Volatile.Read方法强迫location中的值在调用时读取。此外,按照编码顺序,之后的加载和存储操作必须在调用Volatile.Read 之后发生。
  • volatile关键字,它可应用于以下任何类型的静态或实例字段: Boolean,(S)Byte,(U)Int16,(U)Int32,(U)IntPtr,Single和 Char。还可将volatile关键字应用于引用类型的字段,以及基础类型为(S)Byte,(U)Int16或(U)Int32的任何枚举字段。JIT编译器确保对易变字段的所有访问都是以易变读取或写入的方式执行,不必显式调用Volatile 的静态Read或 Write方法。另外,volatile关键字告诉C#和JIT编译器不将字段缓存到CPU的寄存器中,确保字段的所有读写操作都在RAM中进行。(P677 last2)
  • 互锁构造Interlocked:System.Threading.Interlocked。该类中每个方法都执行一次原子读入以及写入操作。所有方法都建立完整内存栅栏(memory fence)。(P678 last)
  • 自旋锁:Interlocked主要用于操作Int32值,如果原子性操作类对象中的一组字段,需要采取一个办法阻止所有线程,只允许其中一个进入对字段进行操作的代码区域。可以使用Interlocked方法构造一个线程同步块。(P682 last)

内核模式构造

  • Windows 提供了几个内核模式的构造来同步线程。内核模式的构造比用户模式的构造慢得多,一个原因是它们要求Windows 操作系统自身的配合,另一个原因是在内核对象上调用的每个方法都造成调用线程从托管代码转换为本机(native)用户模式代码,再转换为本机(native)内核模式代码。然后,还要朝相反的方向一路返回。这些转换需要大量CPU时间;经常执行会对应用程序的总体性能造成负面影响。(P687)
  • 内核模式构造的优点:
    1、内核模式的构造检测到在一个资源上的竞争时,Windows会阻塞输掉的线程,使它不占着一个CPU“自旋”(spinning),无谓地浪费处理器资源。
    2、内核模式的构造可实现本机(native)和托管(managed)线程相互之间的同步。内核模式的构造可同步在同一台机器的不同进程中运行的线程。
    3、内核模式的构造可应用安全性设置,防止未经授权的帐户访问它们。
    4、线程可一直阻塞,直到集合中的所有内核模式构造都可用,或直到集合中的任何内核模式构造可用。
    5、在内核模式的构造上阻塞的线程可指定超时值;指定时间内访问不到希望的资源,线程就可以解除阻塞并执行其他任务。

第30章 混合线程同步构造

  • 合并了用户模式和内核模式构造成为混合线程同步构造。
  • 简单的混合锁。(P697 last)
  • 由于转换为内核模式会造成巨大的性能损失,而且线程占有锁的时间通常都很短,所以为了提升应用程序的总体性能,可以让一个线程在用户模式中“自旋”一小段时间,再让线程转换为内核模式。如果线程正在等待的锁在线程“自旋”期间变得可用,就能避免向内核模式的转换了。(P699 2)
  • FCL中的混合构造(P701)
  • 双检锁技术:开发人员用它将单实例(singleton)对象的构造推迟到应用程序首次请求该对象时进行。这有时也称为延迟初始化(lazyinitialization)。如果应用程序永远不请求对象,对象就永远不会构造,从而节省了时间和内存。但当多个线程同时请求单实例对象时就可能出问题。这个时候必须使用一些线程同步机制确保单实例对象只被构造一次。
  • 条件变量模式(P717 last2)
  • 异步的同步构造(P719)
  • 并发集合类(P723)

LEAVE A COMMENT