当前位置: 首页 > news >正文

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();//主线程调用
    }

线程等待
假设有线程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();
    }
    /// <summary>
    /// 线程优先级,决定该线程可以占用多少cpu时间
    /// </summary>
    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
         * 
         */
    }

前台线程和后台线程
当主程序启动时定义了两个不同的线程。默认情况下,显式创建的线程是前台线程。通过手动的设置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();

           //进程会等所有的前台程序结束完之后才结束;如果只剩下后台程序,则进程会直接结束
       }

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

将线程函数声明为一个类的成员函数,通过类的成员变量来传递参数。
声明一个静态函数当作线程的执行函数,该函数接受一个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在访问变量A的时候,其他线程必须等待直到线程1完成当前操作。
如果锁定了一个对象,需要访问该对象的所有其他线程则会处于阻塞状态,并等待直到该对象解除锁定。这,可能会导致严重的性能问题。
可以使用lock关键字来进行加锁操作
public abstract class BaseCounter
{
public abstract void Add();
public abstract void Del();
public abstract long GetRes();
}

       /// <summary>
       /// 线程不安全的计数器
       /// </summary>
       public class Counter : BaseCounter
       {
           private long counter;
           public override void Add()
           {
               counter++;
           }

           public override void Del()
           {
               counter--;
           }

           public override long GetRes()
           {
               return counter;
           }
       }

       /// <summary>
       /// 线程安全的计数器
       /// </summary>
       public class ThreadSafeCounter : BaseCounter
       {
           /// <summary>
           /// 线程锁对象 
           /// </summary>
           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;
           }
       }

       /// <summary>
       /// 线程函数 测试计数器
       /// </summary>
       /// <param name="c"></param>
       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());

       }

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

相关文章:

  • 网络安全漏洞原理利用与渗透
  • 【DDR3 控制器设计】(4)DDR3 的读操作设计
  • 基于OpenCV的单目相机标定与三维定位(推广)
  • Java数据结构:单链表的实现与面试题汇总
  • 2022年都说软件测试不香了?在职3年月薪16k我满意了,你们觉得前景怎么样?
  • python做了个自动关机工具,再也不会耽误我下班啦
  • BUUCTF NewStarCTF 公开赛赛道Week5 Writeup
  • @Conditional注解详解
  • 动态路由协议解析(rip)
  • 38、Java 中的正则表达式(单字符匹配和预定义字符)
  • 电气论文实现:基于优化算法和python-pandapower的配电网重构(IEEE33节点算例)
  • 刚来的00后真的卷,听说工作还没两年,跳到我们公司直接起薪20k...
  • 【云原生 · Docker】Docker 镜像操作、容器操作常用指令
  • 基于粒子群优化算法的无人机路径规划与轨迹算法的实现(Matlab代码实现)
  • Spring Cloud基本介绍
  • android高仿小视频、应用锁、3种存储库、QQ小红点动画、仿支付宝图表等源码...
  • Apache Spark Streaming 使用实例
  • ES2017异步函数现已正式可用
  • Git 使用集
  • java中的hashCode
  • Js基础知识(一) - 变量
  • miaov-React 最佳入门
  • Python进阶细节
  • Python爬虫--- 1.3 BS4库的解析器
  • Quartz初级教程
  • Selenium实战教程系列(二)---元素定位
  • Spark VS Hadoop:两大大数据分析系统深度解读
  • SwizzleMethod 黑魔法
  • 官方解决所有 npm 全局安装权限问题
  • 精益 React 学习指南 (Lean React)- 1.5 React 与 DOM
  • 前端
  • 如何在GitHub上创建个人博客
  • Redis4.x新特性 -- 萌萌的MEMORY DOCTOR
  • 格斗健身潮牌24KiCK获近千万Pre-A轮融资,用户留存高达9个月 ...
  • #快捷键# 大学四年我常用的软件快捷键大全,教你成为电脑高手!!
  • (2021|NIPS,扩散,无条件分数估计,条件分数估计)无分类器引导扩散
  • (6)设计一个TimeMap
  • (a /b)*c的值
  • (企业 / 公司项目)前端使用pingyin-pro将汉字转成拼音
  • (三)uboot源码分析
  • (十二)springboot实战——SSE服务推送事件案例实现
  • (十三)Java springcloud B2B2C o2o多用户商城 springcloud架构 - SSO单点登录之OAuth2.0 根据token获取用户信息(4)...
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • ... fatal error LINK1120:1个无法解析的外部命令 的解决办法
  • .NET/C# 在代码中测量代码执行耗时的建议(比较系统性能计数器和系统时间)
  • .NET框架类在ASP.NET中的使用(2) ——QA
  • /var/spool/postfix/maildrop 下有大量文件
  • @RequestParam,@RequestBody和@PathVariable 区别
  • [android]-如何在向服务器发送request时附加已保存的cookie数据
  • [Angular] 笔记 8:list/detail 页面以及@Input
  • [C++]C++基础知识概述
  • [datastore@cyberfear.com].Elbie、[thekeyishere@cock.li].Elbie勒索病毒数据怎么处理|数据解密恢复
  • [Electron]ipcMain.on和ipcMain.handle的区别
  • [FC][常见Mapper IRQ研究]
  • [Go WebSocket] 多房间的聊天室(三)自动清理无人房间