• 《CLR Via C# 第3版》笔记之(十七) 线程基础


    最近项目中要用到线程相关的知识,所以先看了本书后面线程的内容,并做了一些总结。

    主要内容:

    • 线程开销
    • 线程的创建
    • 前台和后台线程 

    1. 线程开销

    1.1 线程的概念

    如果没有线程,当应用程序出现死循环时,应用程序所在的进程会一直占据CPU,导致"死机"的现象。

    那么线程是如何避免这种"死机"现象,使得应用程序能更好的响应用户的请求呢?

    Windows系统中引入了线程的概念后,每个进程至少有一个专有线程(相当于这个进程专用的CPU),

    系统已线程为单位分配CPU时间片,如果一个应用程序进入无限循环,那么它的专有线程会"死机"。

    但是其它进程的专有线程不会"死机",所以系统不会停止响应,也不会出现"死机"。

    1.2 线程的结构

    线程还可以增加程序的并发性,对于多CPU的场合,能够提高程序的性能,但同时也会使程序更加复杂。

    相对于进程,线程确实是很"轻量",但是如果在进程中蛮目的增加线程,同样会对系统资源带来很大的负担。

    下面来看看Windows系统中线程的开销有多大?

    线程主要包含以下几个要素:

    线程内核对象。x86(约700字节),x64(约1240字节),IA64(约2500字节)

    包含线程的属性及上下文信息,其中上下文信息包含当前CPU的寄存器信息等。

    当CPU切换线程时,需要将当前线程的寄存器信息保存到上下文中,同时将新线程的上下文复制到CPU寄存器中。

    线程环境块。x86和x64(4KB),IA64(8KB)

    在用户模式中分配,包含线程的异常处理链首,线程本地存储的数据,GDI和OpenGL图形使用的一些数据结构。

    用户模式栈。1MB

    存储方法的实参和局部变量,以及返回的地址。

    内核模式栈。32bit(12KB),64bit(24KB)

    与内核交互时的数据(比如传递给内核的参数)。

    DLL的attach和detach通知。

    创建线程时,调用当前进程加载的所有DLL的DllMain方法,并传递DLL_THREAD_ATTACH标志。

    终止线程时,调用当前进程加载的所有DLL的DllMain方法,并传递DLL_THREAD_DETACH标志。

    通过上面的分析,我们发现每个线程至少占用1MB的内存,资源的消耗并不小,所有要理性的使用它们,只在必须要用线程的地方使用它们。

    2. 线程的创建

    C#中使用线程非常简单,利用System.Threading.Tread类即可。

    Thread的构造函数的参数有2种委托:

    (1) 一种是有参数的委托

    public delegate void ParameterizedThreadStart(object obj);

    (2) 一种是无参数的委托

    public delegate void ThreadStart()

    例子如下:

    using System;
    using System.Threading;
    
    class CLRviaCSharp_17
    {
        static void Main(string[] args)
        {
            Console.WriteLine("This is Main Thread!");
            
            // 有参数的委托
            Thread t1 = new Thread(SubThreadMethod);
            t1.Start("sub thread parameter");
            
            // 无参数的委托
            Thread t2 = new Thread(SubThreadMethod2);
            t2.Start();
    
            Thread.Sleep(4000);
            Console.WriteLine("Main Thread complete!");
            Console.ReadKey(true);
        }
    
        private static void SubThreadMethod(object param)
        {
            Console.WriteLine("This is Sub Thread with parameter : {0}", param);
            Thread.Sleep(1000);
        }
    
        private static void SubThreadMethod2()
        {
            Console.WriteLine("This is Sub Thread without parameter!");
            Thread.Sleep(1000);
        }
    }

    其中Thread t1和t2的执行顺序是不定的,可能t1先执行,也可能t2先执行。

    3. 前台和后台线程

    虽然目前一个CLR中的线程直接对应于一个Windows中的线程,但是以后是有可能分离的。

    而我们目前在C#中使用的线程都是CLR线程。

    CLR线程分为前台线程和后台线程2种。上面例子中直接创建的线程默认为前台线程,用线程池创建的线程默认为后台线程。

    我们应尽量避免使用前台线程,多使用后台线程。

    原因在于:一个进程只有当它的所有前台线程全部终止后才会终止,如果有一个前台线程陷入死循环,那么这个进程就无法自动终止。

    下面来看我们验证的例子:

    首先验证前台线程,新建100个前台线程,每个线程Sleep 5秒,主线程在新建完100个线程后就结束。

    using System.Threading;
    
    class CLRviaCSharp_17
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Thread t = new Thread(ThreadMethod);
                t.Start();
            }
        }
    
        private static void ThreadMethod()
        {
            Thread.Sleep(5000);
        }
    }

    程序运行后并没有立即结束,而是等了5秒才结束。(原因在于有前台线程没终止,进程无法自动终止)

    再验证后台进程,代码差不多,只是在新建线程后把线程的IsBackground属性改为True

    using System.Threading;
    
    class CLRviaCSharp_17
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100; i++)
            {
                Thread t = new Thread(ThreadMethod);
                // 将新建的线程标记为后台线程
                t.IsBackground = true;
                t.Start();
            }
        }
    
        private static void ThreadMethod()
        {
            Thread.Sleep(5000);
        }
    }

    程序运行后立即结束,没有等后台线程5秒。(原因在于所有前台线程已终止,后台进程也自动终止了)

  • 相关阅读:
    [USACO 5.5]Hidden Password
    [Codeforces 1016F]Road Projects
    再会,OI
    [TJOI 2018]智力竞赛
    [POI 2009]Lyz
    [NOI 2015]品酒大会
    [NOI 2017]蔬菜
    [NOI 2017]整数
    [NOI 2017]游戏
    [NOI 2017]蚯蚓排队
  • 原文地址:https://www.cnblogs.com/wang_yb/p/2227846.html
Copyright © 2020-2023  润新知