《CLR via C#》读书笔记

  11年11月份读完《Beginning C# Object》两个月了,期间使用C#重写了原来用C语言实现的矢量地图引擎,有了几万行代码的实践,也用面向对象思想设计重构了引擎,引擎质量提高了一个数量级,可读性、可维护性、性能都有提高;对C#语言的学习更多是在MSDN的文档、《Programming windows phone 7》英文pdf的阅读,结合windows phone平台的使用中,没有系统性的深入学习。因此买了《CLR via C#》、《深入Java虚拟机》两本书,一本老外写的,一本国人写的;一个刚开始学的CLR平台,一个较为熟悉的JVM平台,两者对照学习;JVM的书写的较为简练,更熟悉,读得相对快一些,CLR的书老外的风格,细节、背景很多,页数是JVM的几倍,刚开始觉得啰嗦,需要耐心精心读下去,弄懂每个技术点的来龙去脉,对技术的深入理解收获良多,《Programming windows phone 7》相对Android的文档也是这种感受,写下《CLR via C#》的一些读书笔记:

CLR:定义了一致的语义集、屏蔽上层不同语言的不同语法;用类似汇编语言的中间语言IL描述;

.NET框架类库:让开发人员日子好过一些的常用类。

  最近需要用到多线程,前面读了6章,就先看线程部分救急了:

1.进程是系统分配资源的单位,有虚拟进程空间,可以实现进程间隔离,使得系统健壮安全,不会因为一个应用崩溃而死掉;线程是系统调度的单位,逻辑的CPU,线程有一个标识表明自己所属的进程,线程大概30ms进行一次线程切换,使得OS不会因为一个长时间的任务执行而阻塞不响应用户交互,且可利用多CPU;CLR中的用户线程一一对应一个windows的内核线程(当前JVM的windows、Linux版也是,Linux中线程可理解为共享进程空间的进程),要区分用户模式与内核模式运行,两种模式切换也耗费较多资源;

  线程创建:thread = new Thread(threadOp(param)),thread.Start()启动,Thread.Sleep(), Abort()停止;

2.超线程:硬件概念,为了减少线程上下文切换的消耗,有了超线程的概念,一个CPU只有一组执行资源,但有多组CPU寄存器的线程上下文中需要的资源;

  默认线程上下文有父线程流向子线程,ExectionContext.SuppressFlow()可以阻止流动,提高性能。

  中断上下文与任何进程无关,运行在内核空间。

3.线程池:创建线程耗费资源虽然较进程少,但仍需要线程栈、CPU寄存器等线程上下文,为了减少重复线程创建的消耗,引入了线程池,即创建的线程执行完后不立即结束,由线程池管理,可以重复利用;Thread.QueueUserWorkItem、System.Threading.Timer、TaskScheduler将工作项放到CLR线程池的全局队列中,每个线程再从全局队列取任务到自己的本地工作项线程中;线程池的线程分两种:用于计算的工作者worker线程、用于APM异步IO的IO线程;

  计算操作:ThreadPool.QueueUserWorkItem(computeOp, param);将计算操作computeOp加入线程池操作队列;

    计算操作中间取消:使用CancellationTokenSource,在computeOp中判断;

  IO异步操作:外设如硬盘等可认为是一个微型计算机,CPU给外设任务后,不需要阻塞等待,外设完成任务后以中断方式,通知CPU,再处理外设返回的结果即可;也不需要为每次IO或者每个外设建立一个线程,通过线程池将向外设发送的任务、外设返回结果的处理都通过异步方式作为任务项加入线程池;通常调用BeginXXX(callBack,param)后CLR返回一个唯一标识IO请求IAsyncResult对象,该对象的AsyncState属性为param,param可以存放httpRequest等参数,线程池调用回调函数时通过EndXXX(IAsyncResult)得到外设返回的结果。

    注意点:EndXXX应该唯一调用一次;异步IO操作通常不能取消,只能放弃返回结果;基于Event的EAP模式也可实现某些异步IO(消息队列方式);

4.任务:线程池解决了线程重复创建的消耗,任务解决操作什么时候完成、返回执行结果的问题;新建Task,start()启动,通过task.Wait()等待任务结束;task.ContinueWith(anotherTask)该任务结束后,启动另一个任务;

5.Timer:有两种

  System.Threading.Timer:可将后台任务定期加入线程池;使用注意点:保持timer对象存活防止GC,timer = new Timer(timeOp,param,delayTime,TimeOut.Infinite(-1)),第一次加入时只调用一次,在timeOp中通过timer.Change(delayTime, TimeOut,Infinite)再次加入一次任务;

  System.Windows.Threading.DispatcherTimer:前台,通常用于GUI,将硬件计时器与该线程关联,中断方式将消息加入线程的消息队列;执行回调方法的线程就是设置计时器的线程;如用于向刷新界面的主线程发送消息的System.Windows.Threading.Dispatcher;用法 DispatcherTimer dispatcherTimer = new DispatcherTimer(); dispatcherTimer.Tick += new EventHandler(DispatcherTimerTick);dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 300);dispatcherTimer.Start();dispatcherTimer.Stop();private void DispatcherTimerTick(object sender, EventArgs e){}

6.同步:解决多线程带来的数据读写的线程安全问题,多线程并且可能同时访问的数据才需要同步,如UI界面通过消息队列处理消息则不需要同步,同步分为4种:

  用户模式锁:乐观锁,通过自旋spin的方式,即不使用内核模式的锁,浪费CPU时间,乐观等待其他线程释放锁;如Thread.SpinWait(time);其它如Thread.VolatileWrite(ref,int)、volatile变量、Interlocked中的方法可进行简单的原子操作;

  内核模式锁:悲观锁,需要转换内核模式,比用户模式锁消耗更多时间;AutoResetEvent、ManualResetEvent基于内核的Boolean变量,Semaphore基于内核的Int32变量,Mutex为互斥锁,线程独有,可重复拥有;

  混合锁:混合使用用户模式锁和内核模式锁,由JIT编译器优化,通常先自旋再内核阻塞;如Monitor、ReaderWriterLockSlim等;lock(obj){}是Monitor的语法糖;CLR中实例对象、类对象都有一个同步块索引项,初始-1,Monitor.Enter(obj)后指向一个同步块,同步块中含有对应的内核锁、在该同步块上等待的线程、现在拥有同步块的线程ID等字段,Monitor.Exit(obj)释放锁。

    类似Java的synchronized(obj){}语法糖。

  条件变量模式:结合Monitor、运行条件实现;Monitor.Enter(obj)获得锁,通常在while(条件)循环中条件不满足时通过Monitor.Wait(obj)暂时释放锁,另一个线程通过Monitor.Enter(obj)获得锁后,通过Monitor.Pulse(obj)或者Monitor.PulseAll(obj)唤醒Wait的线程,并Monitor.Exit(obj)释放锁后,在锁上Wait被唤醒的线程可以继续参与运行调度。

    Java中每个Object都有wait()、notify()方法,使用更方便,但是CLR的Monitor语义更好理解。

7.一些语法糖:Parallel.For、ForEach、Invoke一次将多个操作加入线程池;lock(obj){}是try{Monitor.Enter(obj)}finally{Monitor.Exit(obj)};

原文地址:https://www.cnblogs.com/toven/p/2278204.html