《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

相关文章

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

Java中实现线程的创建和启动的方法

《Java中实现线程的创建和启动的方法》在Java中,实现线程的创建和启动是两个不同但紧密相关的概念,理解为什么要启动线程(调用start()方法)而非直接调用run()方法,是掌握多线程编程的关键,... 目录1. 线程的生命周期2. start() vs run() 的本质区别3. 为什么必须通过 st

C#如何去掉文件夹或文件名非法字符

《C#如何去掉文件夹或文件名非法字符》:本文主要介绍C#如何去掉文件夹或文件名非法字符的问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C#去掉文件夹或文件名非法字符net类库提供了非法字符的数组这里还有个小窍门总结C#去掉文件夹或文件名非法字符实现有输入字

C#之List集合去重复对象的实现方法

《C#之List集合去重复对象的实现方法》:本文主要介绍C#之List集合去重复对象的实现方法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录C# List集合去重复对象方法1、测试数据2、测试数据3、知识点补充总结C# List集合去重复对象方法1、测试数据

Linux实现线程同步的多种方式汇总

《Linux实现线程同步的多种方式汇总》本文详细介绍了Linux下线程同步的多种方法,包括互斥锁、自旋锁、信号量以及它们的使用示例,通过这些同步机制,可以解决线程安全问题,防止资源竞争导致的错误,示例... 目录什么是线程同步?一、互斥锁(单人洗手间规则)适用场景:特点:二、条件变量(咖啡厅取餐系统)工作流

Java中常见队列举例详解(非线程安全)

《Java中常见队列举例详解(非线程安全)》队列用于模拟队列这种数据结构,队列通常是指先进先出的容器,:本文主要介绍Java中常见队列(非线程安全)的相关资料,文中通过代码介绍的非常详细,需要的朋... 目录一.队列定义 二.常见接口 三.常见实现类3.1 ArrayDeque3.1.1 实现原理3.1.2

C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式

《C#实现将Office文档(Word/Excel/PDF/PPT)转为Markdown格式》Markdown凭借简洁的语法、优良的可读性,以及对版本控制系统的高度兼容性,逐渐成为最受欢迎的文档格式... 目录为什么要将文档转换为 Markdown 格式使用工具将 Word 文档转换为 Markdown(.

SpringBoot3中使用虚拟线程的完整步骤

《SpringBoot3中使用虚拟线程的完整步骤》在SpringBoot3中使用Java21+的虚拟线程(VirtualThreads)可以显著提升I/O密集型应用的并发能力,这篇文章为大家介绍了详细... 目录1. 环境准备2. 配置虚拟线程方式一:全局启用虚拟线程(Tomcat/Jetty)方式二:异步

Java调用C#动态库的三种方法详解

《Java调用C#动态库的三种方法详解》在这个多语言编程的时代,Java和C#就像两位才华横溢的舞者,各自在不同的舞台上展现着独特的魅力,然而,当它们携手合作时,又会碰撞出怎样绚丽的火花呢?今天,我们... 目录方法1:C++/CLI搭建桥梁——Java ↔ C# 的“翻译官”步骤1:创建C#类库(.NET