• 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
        {
            /// <summary>
            /// 剩余“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
        {
            /// <summary>
            /// 剩余“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++ 内存管理方式
    4.2学习总结
    PTA面向对象程序设计6-3 面积计算器(函数重载)
    3.26学习总结
    PTA——c++面向对象基础
    3.17学习总结.listview用法总结
    3.16学习总结
    3.15学习总结(Python爬取网站数据并存入数据库)
    android开发使用jxl创建Excel
    第一次结对作业
  • 原文地址:https://www.cnblogs.com/jiangxianshen/p/15429943.html
Copyright © 2020-2023  润新知