• C#多线程学习总结


    在这里插入图片描述
    线程的基础概念
    操作系统能够优先访问CPU,并能够调整不同程序访问CPU的优先级,避免某一个程序独占CPU的情况发生。
    可以认为线程是一个虚拟进程,用于独立运行一个特定的程序。
    线程会消耗大量的操作系统资源,多个线程共享一个物理处理器将导致操作系统忙于管理这些线程,而无法运行程序
    在单核cpu上并行执行计算任务是没有意义的。
    在多核cpu上可以使用多线程有效的利用多个cpu的计算能力。这需要组织多个线程间的通信和相互同步。
    线程的基本操作
    ​ 线程的生命周期包括:创建线程 、挂起线程、线程等待、终止线程

    创建线程
    通过new 一个Thread对象并指定线程执行函数创建线程。调用Start方法开启线程

    ///
    /// 线程启动函数
    ///
    static void PrintNums()
    {
    Console.WriteLine(“starting …”);
    for (int i = 0; i < 10; i++)
    {
    Console.WriteLine(i);
    }
    Console.WriteLine(“end…”);
    }
    /// 创建一个线程
    public static void Main()
    {
    Thread thread = new Thread(PrintNums);
    thread.Start();
    PrintNums();//主线程调用
    }
    暂停当前线程
    通过在线程函数中调用Thread.Sleep()暂停当前线程,使线程进入休眠状态。此时线程会占用尽可能少的CPU时间。

    //暂停线程
    static void PrintNumsWithDelay()
    {
    Console.WriteLine(“starting …”);
    //Log(“当前线程状态 :” + Thread.CurrentThread.ThreadState.ToString());
    for (int i = 0; i < 10; i++)
    {
    Thread.Sleep(TimeSpan.FromSeconds(1)); //每次暂停一秒
    Console.WriteLine(i);
    }
    Console.WriteLine(“end…”);
    }

        //创建一个线程并暂停
        public static void Test1()
        {
            Thread t = new Thread(PrintNumsWithDelay);
            t.Start();
            PrintNums();//主线程调用
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    线程等待
    假设有线程t,在主程序中调用了t.Join()方法,该方法允许我们等待直到线程t完成。当线程t完成 时,主程序会继续运行。借助该技术可以实现在两个线程间同步执行步骤。第一个线程会等待另一个线程完成后再继续执行。第一个线程等待时是处于阻塞状态(正如暂停线程中调用 Thread.Sleep方法一样)

    ///
    /// 在主线程中等待线程执行玩
    ///
    public static void Main()
    {
    Thread t1 = new Thread(PrintNumsWithDelay);
    t1.Start();
    t1.Join(); //等待线程t1执行完成,程序会在这里阻塞
    Console.WriteLine(“Thread t1 finished”);
    }
    终止线程
    通过调用Thread.Abort()方法强制终止线程。这会给线程注入ThreadAbortExeception方法,导致线程被终结。这是一个非常危险的操作, 任何时刻发生并可能彻底摧毁应用程序。另外,使用该技术也不一定总能终止线程。目标线程可以通过处理该异常并调用Thread.ResetAbort方法来拒绝被终止。因此并不推荐使用,Abort方法来关闭线程 。

    ///
    /// 终止线程 非常危险,不推荐使用,也不一定能够终止线程
    ///
    public static void Main()
    {
    Thread t = new Thread(PrintNumsWithDelay);
    t.Start();
    //5s之后终止线程t
    Thread.Sleep(5000);
    t.Abort();
    Console.WriteLine(“Thread t has been Abort”);
    }
    获取线程状态
    线程状态位于Thread对象的ThreadState属性中。ThreadState属性是一个C#枚举对象。刚开始线程状态为ThreadState.Unstarted,然后我们启动线程,线程状态会从ThreadState.Running变为ThreadState. WaitSleepJoin。 其中:请注意始终可以通过Thread.CurrentThread静态属性获得当前Thread对象。

    ///
    /// 线程状态
    ///
    public static void Test5()
    {
    Thread t1 = new Thread(PrintNumsWithDelay);
    Log(“t1线程状态 :” + t1.ThreadState.ToString());
    t1.Start();
    Log(“t1线程状态 :” + t1.ThreadState.ToString());
    t1.Join(); //等待线程t1执行完成,程序会在这里阻塞
    Log(“t1线程状态 :” + t1.ThreadState.ToString());
    Console.WriteLine(“Thread t1 finished”);
    Log(“t1线程状态 :” + t1.ThreadState.ToString());
    }
    线程优先级
    通过设置Thread.Priority属性给线程对象设置优先级 ThreadPriority.Highest (最高优先级)、 ThreadPriority.Lowest(最低优先级)。通常优先级更高的线程将获取到更多cpu时间。

    ///
    /// 线程优先级
    ///
    class ThreadSample
    {
    private bool isStop = false;

            public void Stop()
            {
                isStop = true;
            }
    
            public void CountNums()
            {
                long counter = 0;
                while (!isStop)
                {
                    counter++;
                }
                Console.WriteLine("{0} with {1,11} priority has a count = {2,13}"
                    ,Thread.CurrentThread.Name,Thread.CurrentThread.Priority,
                    counter.ToString());
            }
        }
        static void RunThreads()
        {
            //启动两个线程t1 t2
            var sample = new ThreadSample();
            Thread t1 = new Thread(sample.CountNums);
            t1.Name = "Thread1";
            Thread t2 = new Thread(sample.CountNums);
            t2.Name = "Thread2";
            //设置线程的优先级
            t1.Priority = ThreadPriority.Highest;  //t1为最高优先级
            t2.Priority = ThreadPriority.Lowest;  //t2为最低优先级
            //启动
            t1.Start();
            t2.Start();
    
            //主线程阻塞2s
            Thread.Sleep(TimeSpan.FromSeconds(2));
            sample.Stop();  //停止计数
            //等待按键按下
            Console.ReadKey();
        }
        /// 
        /// 线程优先级,决定该线程可以占用多少cpu时间
        /// 
        public static void Test6()
        {
            Log("当前线程的优先级 priority = " + Thread.CurrentThread.Priority.ToString());
            Log("在所有核上运行");
    
            RunThreads();
    
            Thread.Sleep(TimeSpan.FromSeconds(2));
            Log("在单个核上运行");
            //在该进程下的线程只能在一个核上运行
            Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
            //再次执行
            RunThreads();
    
            /*
             结果:多核时高优先级的线程通常会比低优先级的线程多执行次数 但是大体接近
                单核的时候竞争就更激烈了,用有高优先级的线程会占用更多的cpu时间,而留给低优先级的线程的
                cpu时间就更少了。
             * 在所有核上运行
                Thread1 with     Highest priority has a count =     583771892
                Thread2 with      Lowest priority has a count =     444097830
                在单个核上运行
                Thread2 with      Lowest priority has a count =      32457242
                Thread1 with     Highest priority has a count =    6534967709
             * 
             */
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    前台线程和后台线程
    当主程序启动时定义了两个不同的线程。默认情况下,显式创建的线程是前台线程。通过手动的设置Thread对象的IsBackground属性为ture来创建一个后台线程。
    前台线程与后台线程的主要区别: 进程会等待所有的前台线程完成后再结束工作,但是如果只剩下后台线程,则会直接结束工作。
    如果程序定义了一个不会完成的前台线程,主程序并不会正常结束。
    ///
    /// 前台线程和后台线程
    ///

           class ThreadSample2
           {
               private int iterCount = 0;
    
               public ThreadSample2(int count)
               {
                   this.iterCount = count;
               }
    
               public void CountNum()
               {
                   for (int i = 0; i <= iterCount; i++)
                   {
                       Thread.Sleep(500); //挂起0.5s
                       //输出次数
                       Console.WriteLine("{0} prints {1}",Thread.CurrentThread.Name,i);
                   }
    
                   Log("Thread Finished : " + Thread.CurrentThread.Name);
               }
    
           }
           public static void Test7()
           {
               ThreadSample2 samp1 = new ThreadSample2(10);
               ThreadSample2 samp2 = new ThreadSample2(20);
    
               //启动两个线程t1,t2,并讲其中一个设置为后台线程 默认是前台线程
               Thread t1 = new Thread(samp1.CountNum);
               t1.Name = "Foreground";
               Thread t2 = new Thread(samp2.CountNum);
               t2.Name = "Background";
               t2.IsBackground = true;  //设置为后台线程
    
               //启动
               t1.Start();
               t2.Start();
    
               //进程会等所有的前台程序结束完之后才结束;如果只剩下后台程序,则进程会直接结束
           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    向线程中传递参数
    启动线程的时候需要向线程函数中传递参数,一般有三种方式。

    将线程函数声明为一个类的成员函数,通过类的成员变量来传递参数。
    声明一个静态函数当作线程的执行函数,该函数接受一个object类型的参数param,这个参数可以通过Thread.Start(param)传递到线程中。
    通过lambda表达式的闭包机制传递参数,原理等同于1。C#编辑器会帮我们实现这个类。
    ///
    /// 向线程中传递参数
    ///

           public static  void CountNum(int iterCount)
           {
               for (int i = 0; i <= iterCount; i++)
               {
                   Thread.Sleep(500); //挂起0.5s
                   //输出次数
                   Console.WriteLine("{0} prints {1}",Thread.CurrentThread.Name,i);
               }
    
               Log("Thread Finished : " + Thread.CurrentThread.Name);
           }
    
           //方法3:通过object类型的参数传递,参数parma在Start()函数中传递
           public static void Count(object param)
           {
               CountNum((int) param);
           }
    
    
           public static void Test8()
           {
               Thread t1 = new Thread(Count);
               t1.Start(6);  //传递参数
    
               //方法3 通过lamda表达式
               int num = 8;
               Thread t2 = new Thread(()=>{ CountNum(num);});
               num = 12;
               Thread t3 = new Thread(() => { CountNum(num);});
               t2.Start();
               t3.Start();
    
           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    线程锁的使用
    当多个线程同时访问一个资源的时候,容易形成竞争条件,导致错误的产生。为了确保不会发生这种情况,需要保证当前线程1在访问变量A的时候,其他线程必须等待直到线程1完成当前操作。
    如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这,可能会导致严重的性能问题。
    可以使用lock关键字来进行加锁操作
    public abstract class BaseCounter
    {
    public abstract void Add();
    public abstract void Del();
    public abstract long GetRes();
    }

           /// 
           /// 线程不安全的计数器
           /// 
           public class Counter : BaseCounter
           {
               private long counter;
               public override void Add()
               {
                   counter++;
               }
    
               public override void Del()
               {
                   counter--;
               }
    
               public override long GetRes()
               {
                   return counter;
               }
           }
    
           /// 
           /// 线程安全的计数器
           /// 
           public class ThreadSafeCounter : BaseCounter
           {
               /// 
               /// 线程锁对象 
               /// 
               private readonly  object lockObj = new object();
    
               private long counter;
               public override void Add()
               {
                   lock (lockObj)  //锁定一个对象,需要访问该对象的所有其他线程就会处于阻塞状态,并等待直到该对象接触锁定
                   {
                       counter++;
                   }
    
               }
    
               public override void Del()
               {
                   lock (lockObj)
                   {
                       counter--;
                   }
               }
    
               public override long GetRes()
               {
                   return counter;
               }
           }
    
           /// 
           /// 线程函数 测试计数器
           /// 
           /// 
           static void TestCounter(BaseCounter c)
           {
               for (int i = 0; i < 10000000; i++)
               {
                   c.Add();
                   c.Del();
               }
           }
    
           public static void TestLock()
           {
               //测试不安全的计数器
               //创建3个线程同时对计数器进行加减
               Counter c = new Counter();
               Thread t1 = new Thread(() => { TestCounter(c);});
               Thread t2 = new Thread(() => { TestCounter(c);});
               Thread t3 = new Thread(() => { TestCounter(c);});
               t1.Start();
               t2.Start();
               t3.Start();
               t1.Join();
               t2.Join();
               t3.Join();
               //等三个线程都执行完之后打印结果 看是否为0
               Console.WriteLine("count =   " + c.GetRes());
    
    
               //测试线程安全的计数器
               //创建3个线程同时对计数器进行加减
               ThreadSafeCounter c1 = new ThreadSafeCounter();
                t1 = new Thread(() => { TestCounter(c1);});
                t2 = new Thread(() => { TestCounter(c1);});
                t3 = new Thread(() => { TestCounter(c1);});
               t1.Start();
               t2.Start();
               t3.Start();
               t1.Join();
               t2.Join();
               t3.Join();
               //等三个线程都执行完之后打印结果 看是否为0
               Console.WriteLine("count =   " + c1.GetRes());
    
           }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    死锁的产生
    什么是死锁:
    所谓死锁,是指多个进程在运行过程中因争夺资源而造成的一种僵局,当进程处于这种僵持状态时,若无外力作用,它们都将无法再向前推进。 因此我们举个例子来描述,如果此时有一个线程A,按照先锁a再获得锁b的的顺序获得锁,而在此同时又有另外一个线程B,按照先锁b再锁a的顺序获得锁。如下图所示

  • 相关阅读:
    千万不要搞生态啊,除非...
    利用hutool树结构工具-TreeUtil显示多级菜单树状结构
    [Hadoop全分布部署]安装JDK、Hadoop
    java计算机毕业设计校园环境保护监督系统源代码+系统+数据库+lw文档
    【数学建模学习笔记【集训十天】之第五天】
    5分钟制作可直接导入GPTs知识库中的自动爬虫
    卷积神经网络中的卷积核,卷积神经网络大小计算
    AVS3:双向梯度修正BGC
    分享几个适合电脑小白使用清理C盘的方法
    GB/T 20909-2017 钢门窗检测
  • 原文地址:https://blog.csdn.net/weixin_41883890/article/details/127499971