目录 摘要线程中的概念很多,如果没有代码示例来理解,会比较晦涩,而且有些概念落不到实处,因此,本文以一些运行示例代码,结果来阐述线程中的一些基础概念。让自己跟读者一起把线程中的概念理解地更深刻。 1 线程安全1.1 未出现线程抢占class ThreadTest2 { bool done; static void Main() { ThreadTest2 tt = new ThreadTest2(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { done = true; Console.WriteLine("Done"); } } } 运行结果如下: Done 1.2 线程抢占class ThreadTest2 { bool done; static void Main() { ThreadTest2 tt = new ThreadTest2(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { if (!done) { Console.WriteLine("Done"); done = true; } } } 运行结果如下: Done Done 线程抢占例子2: for (int i = 0; i < 10; i++) new Thread (() => Console.Write (i)).Start(); 运行结果 1.3 避免线程抢占class ThreadTest2 { static readonly object locker = new object(); bool done; static void Main() { ThreadTest2 tt = new ThreadTest2(); // Create a common instance new Thread(tt.Go).Start(); tt.Go(); } // Note that Go is now an instance method void Go() { lock (locker) { if (!done) { Console.WriteLine("Done"); done = true; } } } } 运行结果如下: Done 2 线程阻塞class Program { static void Main() { Thread t = new Thread(Go); t.Start(); t.Join(); Console.WriteLine("Thread t has ended!"); } static void Go() { for (int i = 0; i < 1000; i++) Console.Write("y"); } } 运行结果: 1000个y打印完毕才输出"Thread t has ended!"。
3 Thread.yield()和Thread.sleep(0)sleep(0)效果相当于yield(),会让当前线程放弃剩余时间片,进入相同优先级线程队列的队尾,只有排在前面的所有同优先级线程完成调度后,它才能再次获执行的机会。 4 线程如何工作多线痛通过内部的线程调度器(thread scheduler)管理,通过clr委托操作系统。线程调度器会分配适当的执行时间给活动线程,线程等待(锁)或者线程阻塞(用户输入)不会消耗cpu执行时间。 5 线程与进程线程与进程有相似之处。 就像进程在计算机上并行运行一样,多个线程在单个进程中并行运行。 进程彼此完全隔离; 线程的隔离度有限。 特别是,线程与在同一应用程序中运行的其他线程共享(堆)内存。 这就是为什么线程有用的原因:例如,一个线程可以在后台获取数据,而另一个线程可以在数据到达时显示数据。 6 线程的使用和滥用
使用ASP.NET和WCF之类的技术,您如果不知道多线程正在发生-除非您在没有适当锁定的情况下访问共享数据(可能通过静态字段),会破坏线程安全性。 线程之间的交互(通常是通过共享数据),会带来很多复杂性,但却不可避免,因此,有必要将交互保持在最低限度,并尽可能地坚持简单可靠的设计。 好的策略是将多线程逻辑封装到可重用的类中,这些类可以独立检查和测试。 框架本身提供了许多更高级别的线程结构,我们将在后面介绍。 线程化还会在调度和切换线程时(如果活动线程多于CPU内核)会导致资源和CPU的浪费,并且还会产生创建/释放成本。 多线程并不总是可以加快您的应用程序的速度-如果使用过多或使用不当,它甚至可能减慢其速度。 例如,当涉及大量磁盘I / O时,让几个工作线程按顺序运行任务比一次执行10个线程快得多。 7 线程传参7.1 lambda表达式传参最方便的方法就是通过lambda表达式调用匿名方法,传参数。 static void Main() { Thread t = new Thread(() =>Print("Hello from t!")); t.Start(); } static void Print(string message) { Console.WriteLine(message); } 7.2 线程start方法传参static void Main() { Thread t = new Thread(Print); t.Start("Hello from t!"); } static void Print(object messageObj) { string message = (string)messageObj; // We need to cast here Console.WriteLine(message); } 7.3 线程创建需要时间string text = "t1"; Thread t1 = new Thread ( () => Console.WriteLine (text) ); text = "t2"; Thread t2 = new Thread ( () => Console.WriteLine (text) ); t1.Start(); t2.Start(); 运行结果: t2 t2 以上运行结果说明,在t1线程创建之前text被修改成了t2。 8 线程命名每个线程都有名称属性,目的是为了更方便调试。 static void Main() { Thread.CurrentThread.Name = "main"; Thread worker = new Thread(Go); worker.Name = "worker"; worker.Start(); Go(); } static void Go() { Console.WriteLine("Hello from " + Thread.CurrentThread.Name); } 运行结果: Hello from main Hello from worker 9 前台线程与后台线程Thread worker = new Thread(() => Console.ReadLine()); if (args.Length > 0) worker.IsBackground = true; worker.Name = "backThread"; worker.Start(); Console.WriteLine("finish!"); 前台线程会随着主线程窗口关闭而停止,后台线程及时主线程窗口关闭自己独立运行。 10 线程优先级线程优先级决定了操作系统执行活动线程时间的长短。 enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest } 有时候提高了线程的优先级,但却仍然无法满足一些实时的应用需求,这时候就需要提高进程的优先级,System.Diagnostics命名空间中的process进程类. using (Process p = Process.GetCurrentProcess()) p.PriorityClass = ProcessPriorityClass.High; 实际上,ProcessPriorityClass.High比最高优先级低1个级别:Realtime。 将进程优先级设置为Realtime,可指示OS,您永远不希望该进程将CPU时间浪费在另一个进程上。 如果您的程序进入意外的无限循环,您甚至可能会发现操作系统已锁定,只剩下电源按钮可以拯救您! 因此,高通常是实时应用程序的最佳选择。 如果您的实时应用程序具有用户界面,则提高处理优先级将给屏幕更新带来过多的CPU时间,从而减慢整个计算机的速度(尤其是在UI复杂的情况下)。 降低主线程的优先级并提高进程的优先级可确保实时线程不会因屏幕重绘而被抢占,但不会解决使其他应用程序耗尽CPU时间的问题,因为操作系统仍会分配 整个过程的资源不成比例。 理想的解决方案是使实时工作程序和用户界面作为具有不同进程优先级的单独应用程序运行,并通过远程处理或内存映射文件进行通信。 内存映射文件非常适合此任务。 我们将在C#4.0的第14和25章中简要介绍它们的工作原理。 11 异常处理Go无法补捉异常,GoCatch能捕获当前线程的异常,输出Console.WriteLine("exception.");由此可见,线程创建之后,异常只能由本线程捕获,如果其调用方需要捕获,则得用共享内存方式往上传,Task帮我们做了这件事,调用方可在task.result里捕获到其他线程的异常。 public static void Main() { try { new Thread(Go).Start(); Console.ReadKey(); } catch (Exception ex) { // We'll never get here! Console.WriteLine("Exception!"); } } static void Go() { throw null; } // Throws a NullReferenceException static void GoCatch() { try { // ... throw null; // The NullReferenceException will get caught below // ... } catch (Exception ex) { // Typically log the exception, and/or signal another thread // that we've come unstuck // ... Console.WriteLine("exception."); } } 12 线程池当你创建一个线程,几百毫秒会被花费在例如创建本地私有变量堆栈。每个线程都会默认消耗1MB内存,从而允许在非常精细的级别上应用多线程而不会影响性能。当利用多核处理器以“分而治之”的方式并行执行计算密集型代码时,这很有用。 以下方法间接使用线程池: 使用池线程时,需要注意以下几点: 您可以通过Thread.CurrentThread.IsThreadPoolThread属性查询当前是否在线程池上执行。 12.1 通过TPL进入线程池通过Task Parallel Library库中的Task类可轻松使用线程池,Task类由Framework 4.0引入,如果你熟悉老的结构,考虑用不带泛型Task类来替代ThreadPool.QueueUserWorkItem,而泛型Task代表的是一个异步委托。 新的结构更快,更方便,比老的更灵活。 使用不带泛型例子的Task类,调用Task.Factory.StartNew,传递一个目标方法的委托; static void Main() // The Task class is in System.Threading.Tasks { var task=Task.Factory.StartNew(Go); Console.WriteLine("main"); task.Wait() ; Console.WriteLine(task.Result); Console.ReadLine(); } static string Go() { if (Thread.CurrentThread.IsThreadPoolThread) { Console.WriteLine("Hello from the thread pool!"); } else { Console.WriteLine("Hello just from the thread!"); } return "task complete!"; } 输出结果: main Hello from the thread pool! task complete! 12.1.1 Task异常捕获static void Main() // The Task class is in System.Threading.Tasks { var task=Task.Factory.StartNew(Go); Console.WriteLine("main"); try { task.Wait(); } catch (Exception e) { Console.WriteLine("exception!"); } Console.WriteLine(task.Result); Console.ReadLine(); } static string Go() { if (Thread.CurrentThread.IsThreadPoolThread) { Console.WriteLine("Hello from the thread pool!"); } else { Console.WriteLine("Hello just from the thread!"); } throw null; return "task complete!"; } 运行结果,在主线程中捕获到了其他线程的异常: static void Main() { // Start the task executing: Task<string> task = Task.Factory.StartNew<string> ( () => DownloadString ("http://www.") ); // We can do other work here and it will execute in parallel: RunSomeOtherMethod(); // When we need the task's return value, we query its Result property: // If it's still executing, the current thread will now block (wait) // until the task finishes: string result = task.Result; } static string DownloadString (string uri) { using (var wc = new System.Net.WebClient()) return wc.DownloadString (uri); } Task<string> 就是一个返回值为string的异步委托。 12.2 不同过TPL进入线程池如果你的框架是.Net 4.0之前的,你可以不通过Task Parallel Library 进入线程池。 12.2.1 QueueUserWorkItemstatic void Main() { ThreadPool.QueueUserWorkItem(Go); ThreadPool.QueueUserWorkItem(Go, 123); Console.ReadLine(); } static void Go(object data) // data will be null with the first call. { Console.WriteLine("Hello from the thread pool! " + data); } 运行结果: Hello from the thread pool! Hello from the thread pool! 123 与Task不同:
12.2.2 异步委托即鄙人写的这篇文章深入理解C#中的异步(一)——APM模式EAP模式里的2.1APM异步编程模式。
12.3 线程池优化线程池从其池中的一个线程开始。 分配任务后,池管理器会“注入”新线程以应对额外的并发工作负载(最大限制)。 在足够长时间的不活动之后,如果池管理器怀疑这样做会导致更好的吞吐量,则可以“退出”线程。 您还可以通过调用ThreadPool.SetMinThreads设置下限。 下限的作用是微妙的:这是一种高级优化技术,它指示池管理器在达到下限之前不要延迟线程的分配。 当存在阻塞的线程时,提高最小线程数可提高并发性。 ThreadPool.SetMinThreads (50, 50); 13 代码14 参考文章版权声明:本文为博主翻译文章+自己理解,部分代码自己写,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.cnblogs.com/JerryMouseLi/p/14135600.html |
|