线程同步构造

线程同步总体上分为两种构造:用户模式和内核模式。其中用户模式基于CPU指令,内核模式则基于操作系统内核对象。由于用户模式构造由硬件支持,且不发生用户-内核状态切换,因此效率较高。但线程使用基于用户模式的同步时,操作系统也无法捕捉到该线程上的阻塞。因此该线程在操作系统看来总是处于非阻塞状态,这意味着线程总是参与抢占时间片,导致线程无谓的浪费CPU时间。与之相对的是内核模式构造,线程使用操作系统提供的内核对象进行同步,每当进入同步块时,代码需要切换到内核态运行,导致巨大的性能损失。但优点是操作系统可以检测到等待同步对象的线程并将其阻塞,当同步对象可用时再唤醒线程。如果拥有“锁”的线程一直不释放,基于用户模式构造的等待线程将一直在CPU上无意义的运行,称之为“活锁”。基于内核模式构造的等待线程则一直被操作系统阻塞,称之为“死锁”。总体来说“死锁”优于“活锁”,因为前者不消耗CPU时间。

同步I/O

在Windows系统中,应用程序调用I/O函数发起的操作均发生在用户态,而用户态是无法访问I/O硬件设备的,因此Windows把用户态的I/O操作信息打包发送给内核,此时进入内核态访问对应的I/O设备。I/O设备处理请求的操作时,发起I/O请求的线程将被阻塞,只有当I/O操作完成时,才会重新唤醒线程以返回结果。如果在Socket中使用同步I/O操作,当大量客户端请求连接时,将消耗大量线程,造成性能的急剧下降。

使用Task

使用ThreadPool可以方便的发起异步操作,但却无法知道操作在何时完成,且无法直接获取操作的返回值,为此CLR提供了对线程池的再一次包装,即Task类。Task内部实际上也是使用线程池,只不过封装了一些高级功能。

CLR线程池

虽然CLR通过System.Threading名称空间提供了对Windows线程API的封装,但直接创建和销毁线程严重影响性能。过多的线程不仅浪费内存,而且会造成频繁的CPU上下文切换,更重要的是线程大部分时间都处于无所事事的状态,浪费宝贵的CPU时间。其次多线程非常容易引发滥用,特别是那些没什么多线程基础的程序员,为了所谓的“性能”无脑使用线程,即使在一些商业软件中这种现象也比比皆是。为了减少这种现象,CLR提供了原生的线程池支持。对于每个CLR,线程池线程将被所有运行中的托管程序集共享。

CLR内存分配

无论是C#、VB.NET或是Java,这些所谓的高级编程语言实际上都致力于解决C++中常见的内存泄露问题,方法很简单 – 那就是剥夺程序员对于物理内存空间的直接控制权,重新虚拟一层“可控”的内存空间,并由框架(.NET的CLR以及Java的JVM)自行处理内存问题。这样虽然牺牲了一点点性能以及少许灵活性,但大大提高了安全性,更不用说框架封装所带来的便利。




载入天数...载入时分秒...  |  总访问量为
Powered by Github and MarkdownPad 2

--------------------------- 别拉了,我是有底线的>_< ---------------------------