线程基础

1. Windows为什么要支持线程

  线程(thread)职责是对CPU进行虚拟化.

  Windows为每个进程提供了该进程专用的线程(功能相当于一个CPU, 可将线程理解为一个逻辑CPU).

  如果应用程序进入无限循环,与代码相关的进程会被“冻结”,但其他进程可以继续执行.

 

2.专用线程执行异步计算限制操作

  创建一个线程, 并让其执行一次异步计算限制(asunchronous compute-bound)操作

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 
 7 namespace thread1
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             Console.WriteLine("MainThread: start a dedicated thread " + "to do an asynchronous operation");
14             Thread dedicatedThread = new Thread(ComputerBoundOp);  //生成专用线程
15             dedicatedThread.Start(555);  //Thread的Start方法开始执行回调函数, 异步调用一个方法
16 
17             Console.WriteLine("MainThread: Doing Other Work here...");
18             Thread.Sleep(10000);
19             dedicatedThread.Join();  //代表dedicatedThread线程结束后才能继续执行下面代码
20             Console.WriteLine("The End");
21         }
22 
23         private static void ComputerBoundOp(Object state){
24             Console.WriteLine("ComputerBoundOp: state={0}",state);
25             Thread.Sleep(1000); //这个方法返回后,专用线程将结束
26         }
27     }
28 }

  

  有可能后两句话的打印顺序相反, 因为无法控制Windows对两个线程进行调度的方式.

  但是试了几次都是这样的结果( 妈蛋!)

  dedicatedThread.join(); 直到dedicatedThread线程销毁或终止, 才会解除阻塞.

 

3. 前台线程和后台线程

  每个线程要么是前台线程, 要么是后台线程.

  一个进程中所有前台线程结束运行时, 任何后台线程将强制终止, 不会抛出异常

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 
 7 namespace thread1
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13             Thread t = new Thread(Worker);//默认为前台线程
14             t.IsBackground = true; //使线程成为后台线程
15             t.Start();
16         //如果t是一个前台线程,则应用程序大约10秒后才终止 
         //如果t是一个后台线程,则应用程序立即终止
17 Console.WriteLine("Returning from Main"); 18 } 19 20 private static void Worker() 21 { 22 Thread.Sleep(10000); //模拟做10秒钟工作 23 Console.WriteLine("Returning from Worker"); 24 } 25 } 26 }

  结果就是程序立即终止了

 

4. 线程池

  创建和销毁线程都是一个昂贵的操作, 需要耗费大量时间, 太多的线程会浪费内存资源.

  利用线程池可以对线程进行有效的控制,使得线程能够更好的协作。

  线程池将自己的线程划分为 Worker线程 和 I/O线程

  4.1 线程池简单的计算限制操作

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 
 7 namespace thread1
 8 {
 9     class Program
10     {
11         static void Main(string[] args)
12         {
13              Console.WriteLine("MainThread: queuing an asynchronous operation");
14              ThreadPool.QueueUserWorkItem(ComputerBoundOp,555);  
15              Console.WriteLine("MainThread: Doing Other Work here...");
16              Thread.Sleep(10000);
17              Console.WriteLine("The End");
18          }
19  
20          private static void ComputerBoundOp(Object state){
21              Console.WriteLine("ComputerBoundOp: state={0}",state);
22              Thread.Sleep(1000); 
23          }
24     }
25 }

  ThreadPool类可将异步的, 计算限制的操作放到线程池队列中 

1 public static bool QueueUserWorkItem(WaitCallback callBack);
2 public static bool QueueUserWorkItem(WaitCallback callBack, object state);

  那么, 线程池是如何有效的控制系统资源的消耗的呢?

  它的原理非常简单:

  • 线程池中维护一个请求队列,当应用程序有异步的请求时,将此请求(比如请求A)发送到线程池。
  • 线程池将请求A放入请求队列中,然后新建一个线程(比如线程A)来处理请求A。
  • 请求A处理完成后,线程池不会销毁线程A,而是使用线程A来处理请求队列中的下一个请求(比如请求B) (销毁线程会耗费资源)。
  • 当请求过多时,线程池才会再新建一些线程来加快处理请求队列中的请求。(注1
  • 当请求队列为空时,线程池会销毁一些空闲时间比较长的线程。(注2

  注1:保证所有的请求由少量线程处理,减少系统资源的消耗,同时减少了线程新建,销毁的次数。

  注2:空闲时间多长才销毁线程是由CLR决定的,不同版本的CLR这个时间可能不同。

 

5. Task

  参考博客 http://www.cnblogs.com/wang_yb/archive/2011/11/10/2244745.html

  利用ThreadPool的QueueUserWorkItem方法建立的异步操作存在一些限制:

  1. 异步操作没有返回值

  2. 没有内建的机制通知异步操作什么时候完成

  通过任务的状态(TaskStatus),可以了解任务(Task)的生命周期。  

 1 public enum TaskStatus
 2 {   
 3     // 运行前状态
 4     Created = 0,                      // 任务被显式创建,通过Start()开始这个任务
 5     WaitingForActivation = 1,         // 任务被隐式创建,会自动开始
 6     WaitingToRun = 2,                 // 任务已经被调度,但是还没有运行
 7  
 8     // 运行中状态
 9     Running = 3,                      // 任务正在运行
10     WaitingForChildrenToComplete = 4, // 等待子任务完成
11  
12     // 运行完成后状态
13     RanToCompletion = 5,              // 任务正常完成
14     Canceled = 6,                     // 任务被取消
15     Faulted = 7,                      // 任务出错
16 }

  构造一个Task后,它的状态为Create

  启动后,状态变为WaitingToRun

  实际在一个线程上运行时,状态变为Running

  运行完成后,根据实际情况,状态变为RanToCompletiionCanceledFaulted三种中的一种。

  如果Task不是通过new来创建的,而是通过以下某个函数创建的,那么它的状态就是WaitingForActivation

  ContinueWithContinueWhenAllContinueWhenAnyFromAsync。

  如果Task是通过构造一个TaskCompletionSource<TResult>对象来创建的,该Task在创建时也是处于WaitingForActivation状态。

  5.1 创建并启动一个Task

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace thread1
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14              Console.WriteLine("MainThread Start");
15 
16              Task t = new Task(() =>
17              {
18                  Console.WriteLine("Task Start");
19                  Thread.Sleep(1000);
20                  Console.WriteLine("Task End");
21              });
22 
23              t.Start();
24 
25             //主线程没有等待Task, 在Task完成之前就已经完成了 
26             Console.WriteLine("Main Thread end!");
27              Console.ReadKey(true);
28          }
29     }
30 }

  

  5.2 主线程等待子线程完成

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace thread1
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14              Console.WriteLine("MainThread Start");
15 
16              Task t1 = new Task(() =>
17              {
18                  Console.WriteLine("Task1 Start");
19                  Thread.Sleep(1000);
20                  Console.WriteLine("Task1 End");
21              });
22              Task t2 = new Task(() =>
23              {
24                  Console.WriteLine("Task2 Start");
25                  Thread.Sleep(2000);
26                  Console.WriteLine("Task2 End");
27              });
28              t1.Start();
29              t2.Start();
30 
31              // 当t1和t2中任何一个完成后,主线程继续后面的操作
32              // Task.WaitAny(new Task[] { t1, t2 });
33 
34              //当t1和t2中全部完成后,主线程继续后面的操作
35              Task.WaitAll(new Task[] { t1, t2 });
36 
37             //主线程没有等待Task, 在Task完成之前就已经完成了 
38             Console.WriteLine("Main Thread end!");
39              Console.ReadKey(true);
40          }
41     }
42 }

  

   5.3取消Task

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace thread1
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             Console.WriteLine("Main Thread start!");
15             CancellationTokenSource cts = new CancellationTokenSource();
16 
17             // 创建2个Task
18             Task t1 = new Task(() =>
19             {
20                 Console.WriteLine("Task1 start");
21                 for (int i = 0; i < 100; i++)
22                 {
23                     if (!cts.Token.IsCancellationRequested)
24                     {
25                         Console.WriteLine("Count : " + i.ToString());
26                         Thread.Sleep(1000);
27                     }
28                     else
29                     {
30                         Console.WriteLine("Task1 is Cancelled!");
31                         break;
32                     }
33                 }
34                 Console.WriteLine("Task1 end");
35             }, cts.Token);
36 
37             // 启动Task
38             t1.Start();
39             Thread.Sleep(3000);
40             // 运行3秒后取消Task
41             cts.Cancel();
42 
43             // 为了测试取消操作,主线程等待Task完成
44             Task.WaitAny(new Task[] { t1 });
45             Console.WriteLine("Main Thread end!");
46             Console.ReadKey(true);  //等待按键结束
47         }
48     }
49 }

  

 

6. 子任务和任务工厂

  6.1延续任务

  为了保证程序的伸缩性, 应该尽量避免线程阻塞, 这就意味着我们在等待一个任务完成时, 最好不要用Wait, 而是让一个任务结束后自动启动它的下一个任务.

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace thread1
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14            Console.WriteLine("Main Thread start!");
15  
16             // 第一个Task
17             Task<int> t1 = new Task<int>(() =>
18             {
19                 Console.WriteLine("Task 1 start!");
20                 Thread.Sleep(2000);
21                 Console.WriteLine("Task 1 end!");
22                 return 1;
23              });
24  
25             // 启动第一个Task
26             t1.Start();
27             
28             // 因为TaskContinuationOptions.OnlyOnRanToCompletion,
29             // 所以第一个Task正常结束时,启动第二个Task。
30             // TaskContinuationOptions.OnlyOnFaulted,则第一个Task出现异常时,启动第二个Task
31             // 其他可详细参考TaskContinuationOptions定义的各个标志
32             
33             t1.ContinueWith(AnotherTask, TaskContinuationOptions.OnlyOnRanToCompletion);//OnlyOnRanToComplete可以更改为其他参数,如OnlyOnCanceled
34  
35             Console.WriteLine("Main Thread end!");
36             Console.ReadKey(true);
37          }
38  
39         // 第二个Task的处理都在AnotherTask函数中,
40         // 第二个Task的引用其实就是上面ContinueWith函数的返回值。
41         // 这里没有保存第二个Task的引用
42         private static void AnotherTask(Task<int> task)
43         {
44             Console.WriteLine("Task 2 start!");
45             Thread.Sleep(1000);
46             Console.WriteLine("Task 1's return Value is : " + task.Result);  //Task参数传递
47             Console.WriteLine("Task 2 end!");
48         }
49     }
50 }

  

 

  6.2 子任务

  定义子任务时,注意一定要加上TaskCreationOptions.AttachedToParent,这样父任务会等待子任务执行完后才结束

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace thread1
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             Console.WriteLine("Main Thread start!");
15 
16             Task<int[]> parentTask = new Task<int[]>(() =>
17             {
18                 var result = new int[3];
19 
20                 // 子任务1
21                 new Task(() =>
22                 {
23                     Console.WriteLine("sub task 1 start!");
24                     Thread.Sleep(1000);
25                     Console.WriteLine("sub task 1 end!");
26                     result[0] = 1;
27                 }, TaskCreationOptions.AttachedToParent).Start();
28 
29                 // 子任务2
30                 new Task(() =>
31                 {
32                     Console.WriteLine("sub task 2 start!");
33                     Thread.Sleep(1000);
34                     Console.WriteLine("sub task 2 end!");
35                     result[1] = 2;
36                 }, TaskCreationOptions.AttachedToParent).Start();
37 
38                 // 子任务3
39                 new Task(() =>
40                 {
41                     Console.WriteLine("sub task 3 start!");
42                     Thread.Sleep(1000);
43                     Console.WriteLine("sub task 3 end!");
44                     result[2] = 3;
45                 }, TaskCreationOptions.AttachedToParent).Start();
46 
47                 return result;
48             });
49 
50             parentTask.Start();
51 
52             Console.WriteLine("Parent Task's Result is :");
53             foreach (int result in parentTask.Result)
54                 Console.Write("{0}\t", result);
55 
56             Console.WriteLine();
57             Console.WriteLine("Main Thread end!");
58             Console.ReadKey(true);
59         }
60     }
61 }

  

  <1>3个子任务的执行顺序也和定义的顺序无关,比如任务3可能最先执行(与CPU的调度有关)。

  <2>Result打印出来是1 2 3 ,把   TaskCreationOptions.AttachedToParent  删掉试试,打印出来的Result应该是3个0,而不是1  2   3

  

  

  6.3 任务工厂

  可以使用任务工厂来批量创建任务

 1 using System;
 2 using System.Collections.Generic;
 3 using System.Linq;
 4 using System.Text;
 5 using System.Threading;
 6 using System.Threading.Tasks;
 7 
 8 namespace thread1
 9 {
10     class Program
11     {
12         static void Main(string[] args)
13         {
14             Console.WriteLine("Main Thread start!");
15 
16             Task<int[]> parentTask = new Task<int[]>(() =>
17             {
18                 var result = new int[3];
19                 TaskFactory tf = new TaskFactory(TaskCreationOptions.AttachedToParent, TaskContinuationOptions.None);
20 
21                 // 子任务1
22                 tf.StartNew(() =>
23                 {
24                     Console.WriteLine("sub task 1 start!");
25                     Thread.Sleep(1000);
26                     Console.WriteLine("sub task 1 end!");
27                     result[0] = 1;
28                 });
29 
30                 // 子任务2
31                 tf.StartNew(() =>
32                 {
33                     Console.WriteLine("sub task 2 start!");
34                     Thread.Sleep(1000);
35                     Console.WriteLine("sub task 2 end!");
36                     result[1] = 2;
37                 });
38 
39                 // 子任务3
40                 tf.StartNew(() =>
41                 {
42                     Console.WriteLine("sub task 3 start!");
43                     Thread.Sleep(1000);
44                     Console.WriteLine("sub task 3 end!");
45                     result[2] = 3;
46                 });
47 
48                 return result;
49             });
50 
51             parentTask.Start();
52 
53             Console.WriteLine("Parent Task's Result is :");
54             foreach (int result in parentTask.Result)
55                 Console.Write("{0}\t", result);
56 
57             Console.WriteLine();
58             Console.WriteLine("Main Thread end!");
59             Console.ReadKey(true);
60         }
61     }
62 }

  使用任务工厂与上面3.2中直接定义子任务相比,优势主要在于可以共享子任务的设置,比如在TaskFactory中设置TaskCreationOptions.AttachedToParent,  那么它启动的子任务都具有这个属性了。

  当然,任务工厂(TaskFactory)还提供了很多控制子任务的函数,用的时候可以看看它的类定义。

 

7. Parallel并行执行任务

 1 Console.WriteLine("Main Thread start!");
 2         int max = 10;
 3          
 4         // 普通循环
 5         long start = Stopwatch.GetTimestamp();
 6         for (int i = 0; i < max; i++)
 7         {
 8             Thread.Sleep(1000);
 9         }
10         Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - start);
11  
12         // 并行的循环
13         start = Stopwatch.GetTimestamp();
14         Parallel.For(0, max, i => { Thread.Sleep(1000); });
15         Console.WriteLine("{0:N0}", Stopwatch.GetTimestamp() - start);
16  
17         Console.WriteLine("Main Thread end!");
18         Console.ReadKey(true);

  

  Parallel是为了简化任务编程而新增的静态类,利用Parallel可以将平时的循环操作都并行起来

  在上面的例子中,采用并行循环消耗的时间不到原先的一半。

  但是,采用并行循环需要满足一个条件,就是for循环中的内容能够并行才行

  比如for循环中是个对 循环变量i 进行的累加操作(例如sum += i;),那就不能使用并行循环。

 

  还有一点需要注意,Parallel的方法本身有开销

  所以如果for循环内的处理比较简单的话,那么直接用for循环可能更快一些。

  比如将上例中的Thread.Sleep(1000);删掉,再运行程序发现,直接for循环要快很多。

 

posted @ 2014-08-03 19:34  Mirrorhanman  阅读(197)  评论(0编辑  收藏  举报