《CLR via C#》读书笔记-线程同步(三)

2024-01-28 14:40

本文主要是介绍《CLR via C#》读书笔记-线程同步(三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

  • 内核模式的特点
  • 内核模式中相关知识
    • Event构造
    • Semaphore构造
    • Mutex构造
  • 内核模式小节

一、内核模式的特点

  1. 缺点
    保证线程同步可以通过用户模式和内核模式两种方式实现,内核模式与用户模式相比,构造时间要慢得多。主要的原因有两个:1、其需要操作系统的协作。2、在内核对象上调用的方法会按照:托管代码–>本地用户模式代码–>本地内核模式代码的路径转换
  2. 优点
    1. 当发生线程同步竞争时,window会阻塞其中一个线程,但并不会像用户模式占用CPU而“自旋”,避免无谓的浪费
    2. 可实现本地线程与托管线程同步
    3. 可同步在同一电脑上不同进程中运行的线程
    4. 可应用安全性设置,阻止未授权的用户访问

二、内核模式的相关知识
在内核模式的构造中最基本的两个构造就是event构造和semaphore构造,这两个构造的情况会在下面说明。
在整个内核模式中有一个最基本的基类,就是WaitHandle(其在system.threading命名空间内)。其唯一的作用就是封装一个内核对象句柄。在WaitHandle基类中有一属性SafeWaitHandle,可通过这个属性值获取或设定句柄。这个属性值并不是通过外界的赋值而初始化,而是在构造一个WaitHandle派生类时就会初始化(派生类的构造器内部调用了Win32的方法,Win32方法返回了句柄,并将句柄存放在本属性中)。MSDN上属性截图如下所示(也解释了为什么叫SafeWaitHandle,而不是Handle、WaitHandle了):
MSDN上属性的描述

WaitHandle中常用的方法有:

//调用Win32的CloseHandle
public virtual void Close();
public void Dispose();//调用Win32的WaitForSingleObjectEx
public virtual bool WaitOne();
public virtual bool WaitOne(int millisecondsTimeout);//调用Win32的WaitForMultipleObjectEx
public static int WaitAny(WaitHandle[] waitHandle);
public static int WaitAny(WaitHandle[] waitHandle,int millisecondsTimeout);//调用Win32的WaitForMultipleObjectEx
public static bool WaitAll(WaitHandle[] waitHandle);
public static bool WaitAll(WaitHandle[] waitHandle,int millisecondsTimeout);//调用Win32的SignalObjectAndWait
public static bool SignalAndWait(WaitHandle toSignal,WaitHandle toWaitOn);
public static bool SignalAndWait(WaitHandle toSignal,WaitHandle toWaitOn,int millisecondsTimeout,bool exitContext);//保存句柄的属性
public SafeWaitHandle SafeWaitHandle{ get; set;}public const int WaitTimeout=0x102;

上面的方法/属性中,特别是属性SafeWaitHandle,其类型也是SafeWaitHandle,MSDN上的解释:

Represents a wrapper class for a wait handle.

其就是一个句柄的包装类。它的构造方法如下:

public SafeWaitHandle(IntPtr existingHandle,bool ownsHandle
)

因此,SafeWaitHandle类就是一个句柄封装类。
下面详细的逐一说明WaitHandle中的各个方法
1、WaitOne
waitone方法让调用线程等待内核对象收到信号。如果内核对象收到信息,则返回true;若发生超时,则返回false
2、waitany()
waitany方法会让调用线程等待数组内的内核对象收到信号。返回值是int类,是数组中收到信号的内核对象的索引。若在等待期间未有对象收到信号,则返回WaitTimeout
3、waitall()
waitall方法会让调用线程等待数据内的所有的内核对象收到信息。若均收到信号,则返回true;若发生超时,则返回false
4、SignalAndWait
signalandwait方法其会自动向一个内核对象发送信号,并等待另外一个内核对象收到信号。

三、内核对象的具体构造

waithandle基类下会有几个派生类,具体如下

  • waithandle
    • eventwaithandle
      • autoresetevent
      • manualresetevent
  • semaphore
  • mutex

派生类都继承了waithandle类中的方法。除了上面的方法之外,派生类还引入了自己的方法,具体如下:
1、派生类的构造器都在内部调用了Win32的CreateEvent、CreateSemaphore、CreateMutex方法,从这个方法返回的句柄,就保存在一开始说的SafeWaitHandle属性中。
2、三个派生类(EventWaitHandle、Semaphore、Mutex)都提供了一个静态的OpenExisting方法。方法的签名如下

public static Semaphore OpenExisting(string name)

本方法的作用就是判断是否存在名称为name的内核对象。若存在,则返回对象;若不存在,则报异常。
另外,多次调用此方法使用相同的值 name 不一定返回相同 Semaphore 对象。
3、内核模式的应用场景
内核模式最常用的构造就是创建在任何时刻只有一个实例运行的应用程序。这个是由window内核保证的
3.1 event构造
event构造本质上是内核维护的一个bool变量。当事件为false时,在事件上等待的线程就阻塞;当为true时,就解除阻塞。event构造分为:auto和manual。auto代表的是:在解除一个线程的阻塞后(false变为true),event会自动的置为false。而manual代表:当事件置为true之后,不会自动的设为false,因此阻塞的线程会全部的解锁,除非手动的置为true。
EventWaitHandle类代表常用的方法:

public class EventWaitHandle:WaitHandle
{public bool Set();//将事件置为true,解除阻塞,总是返回truepublic bool Reset(); //将事件置为false,使线程阻塞,总是返回true
}//AutoResetEvent与ManualResetEvent的具体定义如下:
public class AutoResetEvent:EventWaitHandle
{public AutoResetEvent(bool initialState);
}public class ManualResetEvent:EventWaitHandle
{public ManualResetEvent(bool initialState);
}

在这儿就可以通过内核模式的autoresetevent来构建一个与之前的simplespinlock相似的线程同步锁。

internal class SimpleWaitLock:IDisposable{pivate AutoResetEvent m_ResourceFree = new AutoResetEvent(true);public void Enter(){m_ResourceFree.WaitOne();//这儿以及SimpleSpinLock类中的Enter方法要想理解Enter的作用及实现原理//Enter方法的本质就是形成一种阻塞,是形成一种对其他(没有获取权限/令牌/资源)线程形成的一种阻塞}public void Leave(){m_ResourceFree.Set();}public void Disposable(){m_ResourceFree.Disposable();}
}

enter方法内部的实现,就是一个阻塞线程的具体逻辑。内核模式的阻塞是通过window内核的内部方法实现。
3.2 Semaphore构造
信号量本质内核维护的一个int变量。信号量为0时,在信号量上等待的线程就会阻塞。信号量大于0时,就解除阻塞。在信号量内部有两个变量:资源剩余量、可处理的最大量。例如:一个教室最多有40个座位,当有同学进入教室时,位于教室门口处的“剩余座位数”就会减一。当有40个同学进入教室时,“剩余座位数”就显示为0,想进入教室的同学只能等待了。基本原理就是这样。
Semaphore类的相关定义如下:

public sealed class Semaphore:WaitHandle
{public Semaphore(int initialCount,int maxCount);public int Release(); //相当于Release(1)public int Release(int releaseCount);
}

Semaphore有一个特点,不会对调用WaitOne或Release方法的线程进行登记。因此会造成一个线程多次调用Release方法,导致“资源剩余量”大于最大处理量,此时就会抛出SemaphoreFullException的异常。举例说明:假设Semaphore最大的处理量为2,此时线程A和线程B均进入(enter)Semaphore,若线程B在释放资源时,调用了Release方法两次。当线程A释放资源,调用Release方法时,Semaphore就会抛出SemaphoreFullException异常。
3.3 Mutex构造
正是因为Semaphore不“认证/登记”线程,存在导致SemaphoreFullException异常的可能,为了解决这个问题,就提出了Mutex(结构体)。Mutex最主要的特性有两个:1、依次允许线程获取资源,但每次只允许一个线程获取资源。2、对线程进行“认证/登记”。Mutex的基本信息如下:

public sealed class Mutex:WaitHandle
{public Mutex();public void ReleaseMutex();
}

Mutex能够保证释放资源的线程就是当初获取资源的线程,但是在线程资源的过程中,因某原因线程被终结了(例如通过任务管理器等),则此时的Mutex的状态就是“遗弃状态”,Mutex会被置为“可用”状态(即,Mutex可被线程占用),此时被阻塞的线程就会获取Mutex内核对象的owership(所有权)。在.NET2.0之前是不会抛出异常的,但是之后就会抛出一个AbandonedMutexException异常。
不管任何原因,一个线程被终结,则系统不能保证在占用Mutex的过程中的数据是完整的,因此一般而言,数据的完整性就被破坏了。但若下一个获得Mutex的线程能够保证数据的完整性,则程序也可以继续进行
在上面的Mutex基本信息介绍中,只是介绍了Mutex的最简单的构造,其还有其他的集中构造方式,如下图所示:
Mutex其他的构造器
Mutex分为两类:未命名的本地Mutex,命名的系统Mutex。两者之间的关系就是电脑属性设定里的全局环境变量和局部环境变量之间的关系。本地的Mutex可以通过Mutex的实例在整个应用程序进程中使用。而系统的Mutex则可以同步不同的进程。
另外,Mutex可以实现递归的调用(这也是拜其本身的特性所赐,能够登记当前调用资源的线程ID),在其内部维护这一个递归计数,用于记录拥有Mutex线程调用/拥有了它多少次。例如:

internal class SomeClass:IDisposable
{private readonly Mutex m_lock = new Mutex(); //声明一个local Mutexpublic void Method1(){m_lock.WaitOne();Method2();m_lock.ReleaseMutex();}public void Method2(){m_lock.WaitOne();//Do somethingm_lock.ReleaseMutex();}public void IDispose(){m_lock.Dispose(); }
}

上面的这个例子中就是一个Mutex使用递归的例子。Mutex可以使用递归更深层的原因就是:每一个本地Mutex都是相互独立。在Method1中占用了资源,并不代表占有method2的资源,因此必须在method2上添加获取及释放方法。另外,Mutex只允许一个线程获得它,并且又提供了提供了递归计数,因此就完美的解决在递归过程中存在的问题。
3.4 内核模式中的回调方法
线程如何知道内核对象收到了信号?一种方式就是阻塞式同步等待,另外一个方式就是非阻塞异步方法。后者就是经常提到的回调方法(callback method)。而在Thread.ThreadPool中也提供了一个方法RegisterWaitForSingleObject,方法的具体定义如下:

public static RegisteredWaitHandle RegisterWaitForSingleObject(WaitHandle waitObject,WaitOrTimerCallback callBack,object state,int millisecondsTimeOutInterval,bool executeOnlyOnce
)

方法的参数如下:

WaitHandle waitObject

代表要监听的内核对象。例如:autosetevent、Semaphore、Mutex等

WaitOrTimerCallback callBack

回调方法。回调方法要符合WaitOrTimerCallback委托。WaitOrTimerCallback委托的定义如下:

public delegate void WaitOrTimerCallback(object state,bool timedOut
)

object state

回调方法的传入参数。回调方法有两个参数,其中第二个参数是指回调方法是因为等到内核对象时间到了还是内核对象自己发出的。为false,代表回调方法之所以被调用,是因为内核对象发出信号。若为true,则代表是因为等待时间到时了。可以通过这个参数,判断是何种情况,参见最下方的例子

int millisecondsTimeOutInterval

代表回调方法的等待时间,一般是-1

bool executeOnlyOnce

是否只执行一次。
针对本方法,举例如下:

internal class RegisteredWaitHandleDemo
{public static void Main(){AutoResetEvent are = new AutoResetEvent(true);RegisteredWaitHandle rwh= ThreadPool.RegisterWaitForSingleObject(are,eventOperation,null,5000,false);Char opeartion = (Char) 0;while(opeartion!='Q'){Console.WriteLine("S=Signal,Q=Quit?");opeartion=Char.ToUpper(Console.ReadKey(true).KeyChar);if(opeartion=='S') are.Set(); }rwh.Unregister(null); //注销的方法}private void eventOperation(object state,bool timeout){Console.WriteLine(timeout?"TimeOut":"Event became true");}
}

四、内核模式小节
内核模式是最纠结的一部分,一是当时就没怎么整明白,要写出来必须自己得明白;二是临近过年,事情比较多。不过好在在春节前完成内核模式部分的读书感,也算没白读。
在读内核模式这部分时,作者给我的最大的感受就是:这个地方写的不是很好,若想了解详细内容,请查看我的《window 核心编程》,撩的我都很想买。要忍住!防止买书如山倒,读书如抽丝的事情。
现在2017-1-22 23:08:33,腊月二十五,以此纪念

这篇关于《CLR via C#》读书笔记-线程同步(三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/653934

相关文章

C#高效实现Word文档内容查找与替换的6种方法

《C#高效实现Word文档内容查找与替换的6种方法》在日常文档处理工作中,尤其是面对大型Word文档时,手动查找、替换文本往往既耗时又容易出错,本文整理了C#查找与替换Word内容的6种方法,大家可以... 目录环境准备方法一:查找文本并替换为新文本方法二:使用正则表达式查找并替换文本方法三:将文本替换为图

C#使用Spire.XLS快速生成多表格Excel文件

《C#使用Spire.XLS快速生成多表格Excel文件》在日常开发中,我们经常需要将业务数据导出为结构清晰的Excel文件,本文将手把手教你使用Spire.XLS这个强大的.NET组件,只需几行C#... 目录一、Spire.XLS核心优势清单1.1 性能碾压:从3秒到0.5秒的质变1.2 批量操作的优雅

C#和Unity中的中介者模式使用方式

《C#和Unity中的中介者模式使用方式》中介者模式通过中介者封装对象交互,降低耦合度,集中控制逻辑,适用于复杂系统组件交互场景,C#中可用事件、委托或MediatR实现,提升可维护性与灵活性... 目录C#中的中介者模式详解一、中介者模式的基本概念1. 定义2. 组成要素3. 模式结构二、中介者模式的特点

C#中SortedSet的具体使用

《C#中SortedSet的具体使用》SortedSet是.NETFramework4.0引入的一个泛型集合类,它实现了一个自动排序的集合,内部使用红黑树数据结构来维护元素的有序性,下面就来介绍一下如... 目录基础概念主要特性创建和初始化基本创建方式自定义比较器基本操作添加和删除元素查询操作范围查询集合运

C# Opacity 不透明度的具体使用

《C#Opacity不透明度的具体使用》本文主要介绍了C#Opacity不透明度的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录WinFormsOpacity以下是一些使用Opacity属性的示例:设置窗体的透明度:设置按钮的透

C#控制台程序同步调用WebApi实现方式

《C#控制台程序同步调用WebApi实现方式》控制台程序作为Job时,需同步调用WebApi以确保获取返回结果后执行后续操作,否则会引发TaskCanceledException异常,同步处理可避免异... 目录同步调用WebApi方法Cls001类里面的写法总结控制台程序一般当作Job使用,有时候需要控制

在ASP.NET项目中如何使用C#生成二维码

《在ASP.NET项目中如何使用C#生成二维码》二维码(QRCode)已广泛应用于网址分享,支付链接等场景,本文将以ASP.NET为示例,演示如何实现输入文本/URL,生成二维码,在线显示与下载的完整... 目录创建前端页面(Index.cshtml)后端二维码生成逻辑(Index.cshtml.cs)总结

C#异步编程ConfigureAwait的使用小结

《C#异步编程ConfigureAwait的使用小结》本文介绍了异步编程在GUI和服务器端应用的优势,详细的介绍了async和await的关键作用,通过实例解析了在UI线程正确使用await.Conf... 异步编程是并发的一种形式,它有两大好处:对于面向终端用户的GUI程序,提高了响应能力对于服务器端应

C#中的Drawing 类案例详解

《C#中的Drawing类案例详解》文章解析WPF与WinForms的Drawing类差异,涵盖命名空间、继承链、常用类及应用场景,通过案例展示如何创建带阴影圆角矩形按钮,强调WPF的轻量、可动画特... 目录一、Drawing 是什么?二、典型用法三、案例:画一个“带阴影的圆角矩形按钮”四、WinForm

C#之枚举类型与随机数详解

《C#之枚举类型与随机数详解》文章讲解了枚举类型的定义与使用方法,包括在main外部声明枚举,用于表示游戏状态和周几状态,枚举值默认从0开始递增,也可手动设置初始值以生成随机数... 目录枚举类型1.定义枚举类型(main外)2.使用生成随机数总结枚举类型1.定义枚举类型(main外)enum 类型名字