• C#线程入门


    前言,多线程在日常编码中经常会用,本文就主线程和子线程之间的关系做个大概的总结,若有差错,欢迎斧正。

    首先打开任务管理器,查看当前运行的进程: 菜单栏右键选择“Task Manager(任务管理)”或 “Ctrl”+“Alt”+“Delete”点击“Task Manager(任务管理)”

    从任务管理器里面可以看到当前所有正在运行的进程。

    Tab点击“性能”:

     可以看到当前正在运行的所有进程数“Processes”和线程数“Threads”

    一般来说一个应用程序就对应一个进程,一个进程可有一个或多个线程即(>=1个线程),有且只有一个主线程。

    进程和线程:

    进程:是Windows系统中的一个基本概念,它包含着一个运行程序所需要的资源。一个正在运行的应用程序在操作系统中被视为一个进程,进程可以包括一个或多个线程。线程是操作系统分配处理器时间的基本单元,在进程中可以有多个线程同时执行代码。进程之间是相对独立的,一个进程无法访问另一个进程的数据(除非利用分布式计算方式),一个进程运行的失败也不会影响其他进程的运行,Windows系统就是利用进程把工作划分为多个独立的区域的。进程可以理解为一个程序的基本边界。是应用程序的一个运行例程,是应用程序的一次动态执行过程。

    线程:是进程中的基本执行单元,是操作系统分配CPU时间的基本单位,一个进程可以包含若干个线程,在进程入口执行的第一个线程被视为这个进程的主线程。在.NET应用程序中,都是以Main()方法作为入口的,当调用此方法时系统就会自动创建一个主线程。线程主要是由CPU寄存器、调用栈和线程本地存储器(Thread Local Storage,TLS)组成的。CPU寄存器主要记录当前所执行线程的状态,调用栈主要用于维护线程所调用到的内存与数据,TLS主要用于存放线程的状态信息。

    前台线程后后台线程:

    前台线程:默认情况下创建的线程都是前台线程,只有所有的前台线程关闭才能完成程序关闭。只有将线程属性IsBackground设置为true,才是后台线程

    后台线程:程序关闭,后台线程不管是否执行完都将关闭

    示例如下:

            static void Main(string[] args)        {            Console.OutputEncoding = Encoding.UTF8;            //前台线程            Thread qtxc = new Thread(() => RunLoop(10));            qtxc.Name = "qiantai thread";            //后台线程            Thread htxc = new Thread(() => RunLoop(20));            htxc.Name = "houtai thread";            htxc.IsBackground = true; //将线程设置为后台线程            //启动线程            qtxc.Start();            htxc.Start();        }        public static void RunLoop(int count)        {            //获取当前线程名称            string threadName = Thread.CurrentThread.Name;            for (int i = 0; i < count; i++)            {                Console.WriteLine($"{threadName}  index:{(i + 1).ToString()}");                //休眠1秒                Thread.Sleep(1000);            }            Console.WriteLine($"{threadName} complete");

    代码中定义了一个循环打印的方法,其中前台线程打印10次,后台线程打印20次,运行结果如下:

    从运行结果可以看出,前台线程打印完成后,后台线程未打印完,但是程序自动结束了。

    然后调整参数,前台线程打印20次,后台线程打印10次:

     class Program    {        static void Main(string[] args)        {            //前台线程            Thread qtxc = new Thread(() => RunLoop(20));            qtxc.Name = "qiantai thread";            //后台线程            Thread htxc = new Thread(() => RunLoop(10));            htxc.Name = "houtai thread";            htxc.IsBackground = true; //将线程设置为后台线程            //启动线程            qtxc.Start();            htxc.Start();        }        public static void RunLoop(int count)        {            //获取当前线程名称            string threadName = Thread.CurrentThread.Name;            for (int i = 0; i < count; i++)            {                Console.WriteLine($"{threadName}  index:{(i + 1).ToString()}");                //休眠1秒                Thread.Sleep(1000);            }            Console.WriteLine($"{threadName} complete");        }    }
     
    

    运行结果如下:

    从运行结果可以看出,后台线程打印完成后,前台线程未打印完继续打印,前台线程打印完成后,程序结束。

    结论就是:前台线程结束时,不管后台线程是否结束,程序都将结束;后台线程结束时,若有前台线程再运行,程序将继续执行,等到前台线程执行完后,程序结束。

    后台线程一般用于处理不重要的事情,应用程序结束时,后台线程是否执行完成对整个应用程序没有影响。如果要执行的事情很重要,需要将线程设置为前台线程。

     主线程和子线程之间的关系:

    • 默认情况,在新开启一个子线程的时候,他是前台线程,只有将线程的IsBackground属性设为true;他才是后台线程

    • 当子线程是前台线程,则主线程结束并不影响其他线程的执行,只有所有前台线程都结束,程序结束

    • 当子线程是后台线程,则主线程的结束,会导致子线程的强迫结束

    • 不管是前台线程还是后台线程,如果线程内出现了异常,都会导致进程的终止。

    • 托管线程池中的线程都是后台线程

    C#中线程的使用:

    引入命名空间:System.Threading

    Thread线程常用方法列表:

    方法说明
    Start开始线程
    Sleep使线程暂停指定的一段时间。如Thread.Sleep(3000),即线程暂停三秒后继续执行
    Abort在线程到达安全点时,使其停止。
    Suspend 在线程到达安全点时,使其暂停。
    Resume重新启动挂起的线程

    **安全点:**
    安全点是指代码中公共语言运行时可以安全地执行自动“垃圾回收”的位置。

    垃圾回收是指释放不再使用的变量并回收内存的过程。调用线程的 Abort 或 Suspend 方法时,公共语言运行时将对代码进行分析,确定让线程停止运行的适当位置。

    Thread常用属性:

    属性说明
    IsAlive 如果线程处于活动状态,则返回TRUE
    IsBackground 是否是后台线程
    Name 获取或设置线程的名称。通常用于在调试时发现各个线程。 
    Priority  获取或设置操作系统用于确定线程调度优先顺序的值
     ApartmentState获取或设置用于特定线程的线程模型。线程模型在线程调用非托管代码时很重要。
    ThreadState 线程状态
    IsThreadPoolThread获取一个值,该值指示线程是否属于托管线程池。
    ManagedThreadId获取当前托管线程的唯一标识符。

    每个线程都有一个优先级属性,用于确定其执行所占用的处理器时间片大小。操作系统为高优先级线程分配较长的时间段,并为低优先级线程分配较短的时间段。新创建的线程具有值 Normal,但可以将 Priority 属性更改为 ThreadPriority 枚举中的任何值

    以上就是Thread类常用属性即方法,其他用法可以查看Thread底层。

    线程同步:

    所谓同步:实质就是在某一时刻,只有一个线程可以访问变量;如果不能确保对变量的访问是同步的,就会产生错误。

    C#为同步访问变量提供了一个非常简单的方式,即使用“Lock”关键词,它可以把一段代码定义为互斥段,互斥段在某一个时刻内,只允许一个线程进入执行,而其他线程必须等待。Lock使用语法如下:

    Lock(expression)
    {
       statement_block
    }

    来个实例,就拿药店卖药为例子:假设药店的“999感冒灵”总量是固定的,然后分发到各个药店出售,一个药店卖出一包,总量就会减少一包,

    再不考虑线程同步的情况下,

    示例如下:

        class Program    {        static void Main(string[] args)        {            //开启两个线程来代表两个分店            Shop shop = new Shop();            //线程1,分店1            Thread thread1 = new Thread(shop.Sell);            thread1.Name = "shop1";            //线程2,分店2            Thread thread2 = new Thread(shop.Sell);            thread2.Name = "shop2";            //开启线程,开始卖药            thread1.Start();            thread2.Start();
                Console.ReadKey();        }
    
        }    public class Shop    {        ///         /// 剩余“999感冒灵”的数量        /// </summary>        public int num = 10;        public void Sell()        {            do            {                string threadName = Thread.CurrentThread.Name;                if (num > 0)                {                    Thread.Sleep(1000);                    num -= 1;                    Console.WriteLine($"{threadName} sell a“999”,stock{num} pack");                }                else                {                    Console.WriteLine("stock nothing!");                }            } while (num > 0);//判断是否有库存,如果有就可以持续出售        }    }

    运行结果:

    从运行结果可以看出,在线程未同步的情况下,两家药店都在销售库存的药,但是店铺1销售的时候不知道库存没有了,于是出现了库存 “-1”的情况,显然是有问题的;

    然后调整方案,加入线程同步,示例如下:

     class Program    {        static void Main(string[] args)        {            //开启两个线程来代表两个分店            Shop shop = new Shop();            //线程1,分店1            Thread thread1 = new Thread(shop.Sell);            thread1.Name = "shop1";            //线程2,分店2            Thread thread2 = new Thread(shop.Sell);            thread2.Name = "shop2";            //开启线程,开始卖药            thread1.Start();            thread2.Start();
                Console.ReadKey();        }
    
        }    public class Shop    {        ///         /// 剩余“999感冒灵”的数量        /// </summary>        public int num = 10;        public void Sell()        {            do            {                string threadName = Thread.CurrentThread.Name;                lock (this) //使用lock关键字解决线程同步的问题                {                    if (num > 0)                    {                        Thread.Sleep(1000);                        num -= 1;                        Console.WriteLine($"{threadName} sell a“999”,stock{num} pack");                    }                    else                    {                        Console.WriteLine("stock nothing!");                    }                }            } while (num > 0);//判断是否有库存,如果有就可以持续出售        }    }
     
    

    运行结果如下:

     从运行结果可以看出,在线程同步的情况下,每个药店都从库存里面拿药,药店1出售的时候,药店2就等着,当药店1销售完最后一包药,就没有另外的销售记录了,无论是药店1还是药店2来拿药,库存都没有了,这就是线程同步的重要性!

    结论:expression代表你希望跟踪的对象:
               如果你想保护一个类的实例,一般地,你可以使用this;
               如果你想保护一个静态变量(如互斥代码段在一个静态方法内部),一般使用类名就可以了
              而statement_block就算互斥段的代码,这段代码在一个时刻内只可能被一个线程执行。

     以上就是线程的入门级教程~

    不积跬步,无以至千里;不积小流,无以成江海。ヾ(◍°∇°◍)ノ゙

  • 相关阅读:
    使用c++实现简易线程池
    python每日一题【剑指 Offer 63. 股票的最大利润】
    阿里云99元服务器新老用户同享396元4年!
    在哪儿比较好下载建筑学西方近现代的外文文献?
    word软件中硬件图像加速有什么用处?禁用硬件图形加速(G)会影响word文档中插入图片的分辨率吗?
    计算机网络(TCP协议)
    UI5:面向企业级应用的JavaScript框架
    使用Qt模仿文字浮动字母
    【2024年更新】大数据专业毕设必过选题推荐
    docker部署prometheus+grafana服务器监控(三) - 配置grafana
  • 原文地址:https://blog.csdn.net/biyusr/article/details/126364168