.Net基础--线程

线程(Thread)

线程(thread)是操作系统能够进行运算调度的最小单位,也就是程序中的一个执行流。(其实可以分为操作系统内核调度的内核线程和用户空间调度的用户线程。在编程中我们创建的线程都是用户线程,本文中的线程是指用户线程,非内核线程)。

线程分为前台线程和后台线程。

  • 前台线程:主程序必须等待前台线程执行完毕才能结束。
  • 后台线程:主程序无需等待后台线程执行完毕就可关闭,后台线程随着主程序关闭而结束。

clr中每个线程的建立都会分配到至少1M的内存空间,主要是线程中用户模式栈所占用

进程和线程

在说道线程,就不得不说下进程的概念:

进程是操作系统中进行保护和资源分配的基本单位,操作系统分配资源以进程为基本单位。可以理解为每一个启动/运行的程序就是一个进程。

进程和线程的关系

一个进程至少包含一个线程(主线程),也可以是包含多个线程。

 

 

 

.Net中简单操作线程

 1         public void TestThread()
 2         {
 3             int currentThreadId = Thread.CurrentThread.ManagedThreadId;   ///当前线程的Id
 4 
 5             ///创建Thread
 6             var thread1 = new Thread(new ThreadStart(SendEmail));
 7             thread1.Start(); 
 8             var thread2 = new Thread(new  ParameterizedThreadStart(SendMessage));
 9             thread2.Start("15933628754");
10 
11             //使用lambda构建
12             var thread3 = new Thread(p => SendEmail());
13             thread3.Start();
14             var thread4 = new Thread(p => SendMessage("52258844"));
15             thread4.Start();
16         }
17         /// <summary>
18         /// 发送呢邮件
19         /// </summary>
20         private void SendEmail()
21         {
22 
23             //TODO send email
24             Thread.Sleep(5000);    //线程休眠5秒
25             Console.WriteLine("send the email completed");
26         }
27         /// <summary>
28         /// 发送消息
29         /// </summary>
30         /// <param name="email"></param>
31         public void SendMessage(object email)
32         {
33             //TODO send message
34         }
View Code

 

Net中Thread提供操作的方法有

  • Start():启动线程;

  • Sleep(int):静态方法,暂停当前线程指定的毫秒数;

  • Abort():在调用此方法的线程上引发 ThreadAbortException,以开始终止此线程的过程。调用此方法通常会终止线程。

  • Interrupt: 中断处于 Wait/Sleep/Join 线程状态的线程。

  • Join:在继续执行标准的 COM 和 SendMessage 消息泵处理期间,阻塞调用线程,直到某个线程终止为止。此方法有不同的重载形式。

 

多线程

多线程是指程序(进程)中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务。

cpu的运行速度是非常快的,很多情况下都属于空闲状态。当程序仅适用单个线程去顺序处理业务时,对于一些相对庞杂的业务处理时,需要等待很久才可能执行完成。为了能够充分利用cpu资源,快速的处理业务,我们常常会将程序中创建多个线程来处理业务。

多线程的优势:

  • 提高cpu利用率;
  • 提高程序效率;
  • 提高用户体验;

多线程的缺点:

  • 每个线程至少占用1m的内存空间,如果线程过多可能导致内存溢出。
  • 多线程情况下可能会导致业务错乱问题,需要再设计时充分考虑资源共享同步问题(该加锁的加锁,加锁后还要注意可能的死锁问题)。
  • 线程过多并且业务处理时间较久时,可能导致cpu在不同线程之间频繁切换,也可能导致性能问题

 

总体而言,多线程的优势是非常明显的,特别是在处理高并发,业务复杂度比较庞杂的情况下尤为重要。而其缺点,我们可以根据代码进行控制,避免。

 线程池(ThreadPool)

线程池是统一管理(创建,销毁,使用等)其创建线程的容器。

  • 频繁的创建/销毁线程是比较耗费系统资源的,引入线程池则可以降低频繁的创建/销毁线程,线程可以重复利用,提高程序性能。
  • 线程池中的线程才是由线程池管理的,初始化时线程池中没有线程,调用线程池函数是将任务插入都线程池的全局队列中,在消费全局队列中的消息时,线程池才去判断是否是创建线程处理消息(如果是线程池已满,则不会创建,等待空闲的线程自行处理消费消息)。
  • 同时运行过多的线程可能会导致程序,甚至是系统崩溃。所以在使用线程池时也要注意线程数的控制。

简单线程结构图

 

调用线程池处理任务的方法其实先将工作项放到全局队列中(注意这里不是存放在线程的本地队列中的,所以在取出任务时会涉及线程同步锁)

由于是多个工作者线程在全局队列中拿走工作项,这就会形成并发情形,要有一个线程同步锁,保证两个或多个线程不会获取同一个工作项。这个线程同步锁在某些应用程序中可能成为瓶颈。工作者线程处理任务流程:

  1. 工作者线程首先是从其对应的本地队列的列尾(后进先出)中尝试获取工作项,如果成功则处理它(只用对应线程才可操作列尾(栈顶),所以无需线程同步锁)
  2. 如果工作者线程对应的本地队列为空 , 该工作者线程会从尝试从其他的线程的本地队列的列头获取任务来执行(可能有多个其他线程同时操作,所以此过程要求一个线程同步锁)。
  3. 若果所有的本地队列都是空的,则工作者线程会从全局队列的列头(先进先出)取一个工作项 .(可能有多个其他线程同时操作,此过程要求一个线程同步锁)
  4. 如果全局队列也为空 , 工作者线程会进入睡眠状态 , 等待事情的发生 .
  5. 如果睡眠时间太长了, 他会自己醒来, 销毁自身, 允许系统回收线程使用的资源( 内核对象, 栈等)。

.Net中简单使用线程池示例

        public void TestThreadPool()
        {
            #region 全局设置
            //第一参数是设置线程池辅助线程,第二个值是设置异步I/O线程  
            // 异步 I/O 是用于处理I/O完成端口的线程。区别在于,系统如何为I/O操作和非I/O操作分配线程。
            //当程序是I/O密集型时,你应该将线程数向I/O偏移,如果程序是CPU密集型时,则将线程数向工作线程偏移。
            ThreadPool.SetMaxThreads(32, 32);
            ThreadPool.SetMinThreads(4, 4);
            #endregion
            //实际从接口中获取行情数据
            var quotationInfo = new QuotationInfo()
            {
                Code = "Gold",
                CurrentPrice = 299.85m,
                Time = DateTime.Now,
            };
            ThreadPool.QueueUserWorkItem(new WaitCallback(ManageQuotation), quotationInfo);
        }

        private void ManageQuotation(object quotation)
        {
            QuotationInfo quotationInfo = quotation as QuotationInfo;
            //TODO处理行情数据

        }
View Code

 

Task

 线程池通过维护线程,使得线程可以重复利用,减少了频繁创建、回收线程导致的性能问题。但是使用线程池方法处理任务的缺点也非常明显

  • 无法获取执行结果。
  • 无法知道什么时候执行完成,缺少控制线程的api(调用 ThreadPool.QueueUserWorkItem方法后就基本全部交给cpu了)。也无法控制不同线程执行顺序。
  • 任务存放在全局队列中,而全局队列的线程同步锁可能会造成性能瓶颈。

Task是.NET Framework 4.0才开始出现的。Task是为了更好的实现异步编程,Task提供了丰富的API来管理线程、控制执行顺序,获取执行结果等。它的本质是使用ThreadPool,但它的任务不是存放在线程池的全局队列中,而是存放在线程池的本地队列中,使线程之间的资源竞争减少(全局队列是加了线程同步锁的,而本地队列则没有)。

Task的简单代码

 public   void TaskTest()
        {
            Console.WriteLine("主线程Id  " + Thread.CurrentThread.ManagedThreadId.ToString());

            //创建task方式1:new Task(),  如果是有返回值的则使用 new  Task<TResult>()。注意调用task.Start()才会启动
            var task1 = new Task(() => {
                Task.Delay(100);
                Console.WriteLine("new Task() " + Thread.CurrentThread.ManagedThreadId.ToString());
            });
            task1.Start();

            //方式2 Task.Run(),如果是有返回值的则使用 Task.Run<TResult>()
            Task.Run(() =>
            {
                Task.Delay(10);
                Console.WriteLine("Task.Run  " + Thread.CurrentThread.ManagedThreadId.ToString());
            });

            ///方式3 Task.Factory.StartNew() 如果是有返回值则调用Task.Factroy.Startnew<TResult>()
            Task.Factory.StartNew(() =>
            {
                Task.Delay(10);
                Console.WriteLine("create by TaskFactory " + Thread.CurrentThread.ManagedThreadId.ToString());
            }); 
        }
View Code

 

Async 和Await

async用于声明异步方法。await限定调用。

  • 方法头使用async修饰,标识为一个异步方法。
  • 方法体可包含0个或者多个await表达式(一个await限定表达式都没有时,即为同步方法,编码时vs报警告)。
  • 参数个数不限定,但是不可包含ref或者out限定参数。
  • 返回值只能限定为void,Task,Task<T>
  • 一般以Async作为方法后缀

关于异步返回值:

  • void:调用方法执行异步方法,但又不需要做进一步的交互。
  • Task:调用方法不需要从异步方法中取返回值,但是希望检查异步方法的状态,那么可以选择可以返回 Task 类型的对象。所以,返回值为Task的异步方法中包含 return 语句,也不会返回任何值。
  • Task<T>:调用方法要从调用中获取一个 T 类型的值,异步方法的返回类型就必须是Task<T>。调用方法从 Task 的 Result 属性获取的就是 T 类型的值。

调用异步方法简单流程(注:异步方法内部调用另一个async声明的方法,如果它使用了await限定,其对应方法调用为同步等待;如果调用的异步方法不用await声明则会创建task处理的

 

 

 代码测试,注意Task.Delay(1000)方法。这是异步的等待,与Thread.Sleep是有区别的。因为Await Task.Delay,所以相当于需异步等待执行结果,也就是将任务插入线程池队列中,空闲的线程会消费这个任务。

        public async void TaskTest()
        {
            Console.WriteLine("主线程Id  "+Thread.CurrentThread.ManagedThreadId.ToString());

            var task1 = new Task(() => {
                Console.WriteLine("task1 " + Thread.CurrentThread.ManagedThreadId.ToString());
             });
            task1.Start();

           var xx=await ShowAsyncThread();
            Console.WriteLine(xx);
            var taskl2 = Task.Factory.StartNew(()=> {
                Console.WriteLine("task2 "+Thread.CurrentThread.ManagedThreadId.ToString());
            });

        }

        private async Task<string> ShowAsyncThread()
        {
            Console.WriteLine("async "+Thread.CurrentThread.ManagedThreadId.ToString());
            await Task.Delay(1000);
            await ShowAsyncThread2();
            Console.WriteLine("async completed " + Thread.CurrentThread.ManagedThreadId.ToString());
            return "xxx";
        }
        private async Task<string> ShowAsyncThread2()
        {
            Console.WriteLine("async2 " + Thread.CurrentThread.ManagedThreadId.ToString());
            await Task.Delay(1000); 
            Console.WriteLine("async2 completed " + Thread.CurrentThread.ManagedThreadId.ToString());
            return "xxx";
        }
    }
View Code

结果

 

 

--------

以上

posted on 2020-12-14 23:41  john_yong  阅读(556)  评论(0编辑  收藏  举报

导航