1. 进程和线程的概念 线程 对于所有需要等待的操作,例如移动文件,数据库和网络访问都需要一定的时间,此时就可以启动一个新的线程,同时完成其他任务。
一个进程的多个线程 可以同时运行在不同的CPU上或多核CPU的不同内核上。
线程是程序中独立的指令流。
在VS编辑器中输入代码的时候,系统会分析代码,用下划线标注遗漏的分号或其他语法错误,这就是用一个后台线程完成。
Word文档需要一个线程等待用户输入,另一个线程进行后台搜索,第三个线程将写入的数据存储在临时文件中。
运行在服务器上的应用程序中等待客户请求的线程称为侦听器线程。
进程 进程包含资源,如WIndows句柄,文件系统句柄或其他内核对象。每个进程都分配了虚拟内存,一个进程至少包含一个线程 。
一个程序启动,一般会启动一个进程,然后进程启动多个线程。
进程和线程的一个简单解释(资料来源-阮一峰)
计算机的核心是CPU,它承担了所有的计算任务。它就像一座工厂,时刻在运行。
如果工厂的电力有限一次只能供给一个车间使用。也就是说一个车间开工的时候,其他车间就必须停工。背后的含义就是单个CPU一次只能运行一个任务(多核CPU可以运行多个任务)。
进程就好比工厂的车间,它代表CPU所能处理的单个任务。任一时刻,CPU总是运行一个进程,其他进程处于非运行状态。
一个车间里可以有很多工人,他们协同完成一个任务。
线程就好比车间里的工人,一个进程可以包括多个线程。
车间的控件是工人们共享的,比如许多房间是每个工人都可以进出的。这象征一个进程的内存空间是共享的,每个线程都可以使用这些共享空间。
一个防止他人进入的简单方法,就是门口加一把锁(厕所)。先到的人锁上门,后到的人看到上锁,就在门口排队,等锁打开再进去。这就叫”互斥锁”(Mutual exclusion,缩写为Mutex)防止多个线程同时读写某一块内存区域。
还有些房间,可以同时容纳n个人,比如厨房。也就是说,如果人数大于n,多出来的人只能在外面等着。这好比某些内存区域,只能供给固定数目的线程使用。
这时的解决方法,就是在门口挂n把钥匙。进去的人就取一把钥匙,出来时再把钥匙挂回原处。后到的人发现钥匙架空了,就知道必须在门口排队等着了。这种做法叫做”信号量”(Semaphore),用来保证多个线程不会互相冲突。
不难看出,Mutex是Semaphore的一种特殊情况(n=1时)。也就是说,完全可以用后者替代前者。但是,因为Mutex较为简单且效率高,所以在必须保证资源独占的情况下,可以采用这种设计。
操作系统的设计,因此可以归结为三点:
以多进程形式,允许多个任务同时运行;
以多线程形式,允许单个任务分成不同的部分运行;
提供协调机制,一方面防止进程之间和线程之间产生冲突,另一方面允许进程之间和线程之间共享资源。
2. 线程开启方式-异步委托(BeginInvoke) 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Program { static void Test () { Console.WriteLine("Test" ); } static void Main (string [] args ) { Action a = Test; a.Invoke(); Console.WriteLine("Main" ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Program { static void Test (int i,string str ) { Console.WriteLine("Test" + i + str); } static void Main (string [] args ) { Action<int ,string > a = Test; a.Invoke(66 ,"AAA" ); Console.WriteLine("Main" ); } }
有参有返回值线程
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 using System;using System.Threading;namespace Thread_Task_and_Sync_Tset { class Thread_Test1 { static int Test (string str,int i ) { Console.WriteLine("Test" +str+i); Thread.Sleep(100 ); return 100 ; } static void Main (string [] args ) { Func<string ,int ,int > a = Test; IAsyncResult ar = a.BeginInvoke("Fuck" , 66 , null , null ); Console.WriteLine("Main" ); while (ar.IsCompleted == false ) { Console.Write(". " ); Thread.Sleep(10 ); } int res = a.EndInvoke(ar); Console.WriteLine(res); } } }
Invoke
与begininvoke
区别在于,Invoke
会阻塞当前线程,直到Invoke
调用结束,才会继续执行下去,而begininvoke
则可以异步进行调用,也就是该方法封送完毕后马上返回,不会等待委托方法的执行结束,调用者线程将不会被阻塞。
3. 检测委托线程的结束(通过等待句柄AsyncWaitHandler和回调函数) AsyncWaitHandler
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 using System;using System.Threading;namespace Thread_Task_and_Sync_Tset { class Thread_Test1 { static int Test (string str,int i ) { Console.WriteLine("Test" +str+i); Thread.Sleep(1000 ); return 100 ; } static void Main (string [] args ) { Func<string ,int ,int > a = Test; IAsyncResult ar = a.BeginInvoke("Fuck" , 66 , null , null ); bool isFinished = ar.AsyncWaitHandle.WaitOne(1100 ); if (isFinished) { int res = a.EndInvoke(ar); Console.WriteLine(res); } Console.WriteLine("Main" ); } } }
回调函数
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 using System;using System.Threading;namespace Thread_Task_and_Sync_Tset { class Thread_Test1 { static int Test (string str,int i ) { Console.WriteLine("Test" +str+i); Thread.Sleep(1000 ); return 100 ; } static void Main (string [] args ) { Func<string ,int ,int > a = Test; IAsyncResult ar = a.BeginInvoke("Fuck" , 66 , OnCallBack,a); Console.ReadKey(); } static void OnCallBack (IAsyncResult ar ) { Func<string , int , int > a = ar.AsyncState as Func<string ,int ,int >; int res = a.EndInvoke(ar); Console.WriteLine(res); } } }
直接使用lambda表达式来写回调函数内容
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 using System;using System.Threading;namespace Thread_Task_and_Sync_Tset { class Thread_Test1 { static int Test (string str,int i ) { Console.WriteLine("Test" +str+i); Thread.Sleep(1000 ); return 100 ; } static void Main (string [] args ) { Func<string ,int ,int > a = Test; a.BeginInvoke("Fuck" , 66 , ar => { int res = a.EndInvoke(ar); Console.WriteLine(res); },null ); Console.ReadKey(); } } }
结果与上图相同
4. 线程开启方式2-通过Thread类 Thread类 使用Thread类可以创建和控制线程。Thread构造函数的参数是一个无参无返回值的委托类型(Action)
1 2 3 4 5 6 7 8 static void Main () { var t1 = new Thread(ThreadMain); t1.Start(); Console.WriteLine("This is the main thread." ); } static void ThreadMain () { Console.WriteLine("Running in a thread." ); }
在这里哪个先输出是无法保证的,线程的执行由操作系统决定,我们只能知道Main线程和分支线程是同步执行的。在Thread的构造函数中传递一个方法,然后调用Thread.Start()
,就会开启一个线程去执行传递的方法。
Thread类-Lambda表达式 既然Thread的构造参数是委托类型,那么肯定可以用Lambda表达式
1 2 3 4 5 static void Main () { var t1 = new Thread( ()=> Console.WriteLine("Running in a thread,id : " + Thread.CurrentThread.ManangedThreadId) ); t1.Start(); Console.WriteLine("This is the main Thread. ID: " + Thread.CurrentThread.ManangedThreadId); }
 Thread类是可以接受以Object为参数的方法的
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 namespace ThreadClass { class ThreadClassTest { static void MethodForThread1 () { Thread.Sleep(1000 ); Console.WriteLine("This is for Thread without param" ); } static void MethodForThread2 (object something ) { Thread.Sleep(2000 ); Console.WriteLine("This is for Thread with param" +something); } static void Main (string [] args ) { Thread t1 = new Thread(MethodForThread1); t1.Start(); Thread t2 = new Thread(MethodForThread2); t2.Start("333" ); Thread t3 = new Thread(() => { Thread.Sleep(3000 ); Console.WriteLine("This is for Thread with Lambda" ); }); t3.Start(); } } }
给线程传递数据-通过委托 给线程传递一些数据可以采用两种方式,一种方式是使用带ParameterizedThreadStart委托参数的Thread构造函数,一种方式是创建一个自定义类,把线程的方法定义为实例方法,这样就可以初始化实例数据,之后启动线程。
1 2 3 4 5 6 7 8 9 10 11 12 public struct Data{ public string Message; } static void ThreadMainParameters (Object o ) { Data d = (Data)o; Console.WriteLine("Running in a thread,received : " + d.Message); } static void Main () { var d = new Data(Message = "Info" ); var t2 = new Thread(ThreadMainWithParameters); t2.Start(d); }
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 namespace ThreadClass { class ClassForThread { private string fileName; private string filePath; public ClassForThread (string fileName,string filePath ) { this .fileName = fileName; this .filePath = filePath; } public void Download () { Console.WriteLine("现在正在开始下载" +fileName+filePath); Thread.Sleep(1000 ); Console.WriteLine("下载完成" ); } } class ThreadClassTest { static void Main (string [] args ) { ClassForThread myThread = new ClassForThread("小黄片儿" , "C://My Documents" ); Thread myt = new Thread(myThread.Download); myt.Start(); Console.ReadKey(); } } }
5. 线程的其它概念后台和前台线程,线程的优先级,线程的状态 后台和前台线程 只有一个前台线程在运行,应用程序的进程就在运行,如果多个前台线程在运行,但是Main方法结束了,应用程序的进程仍然是运行的,直到所有的前台线程完成其任务为止。
在默认情况下,用Thread类的创建的线程是前台线程。线程池中的线程总是后台线程。
在用Thread类创建线程的时候,可以设置IsBackground属性,表示它是一个前台线程还是一个后台线程。
看下面例子中前台线程和后台线程的区别:
1 2 3 4 5 6 7 8 9 10 11 12 class Program { static void Main () { var t1 = new Thread(ThreadMain){IsBackground = false }; t1.Start(); Console.WriteLine("Main thread ending now." ); } static void ThreadMain () { Console.WriteLine("Thread +" +Thread.CurrentThread.Name + "started" ); Thread.Sleep(3000 ); Console.WriteLine("Thread +" +Thread.CurrentThread.Name + "started" ); } }
后台线程用的地方:如果关闭Word应用程序,拼写检查器继续运行就没有意义了,在关闭应用程序的时候,拼写检查线程就可以关闭。
当所有的前台线程运行完毕,如果还有后台线程运行的话,所有的后台线程会被终止掉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 namespace ThreadClass { class ThreadClassTest { static void MethodForThread3 (object obj ) { Console.WriteLine("Downloading..." + Thread.CurrentThread.ManagedThreadId + obj); Thread.Sleep(2000 ); Console.WriteLine("Download Complete" ); } static void Main (string [] args ) { Thread mythread = new Thread(MethodForThread3); mythread.IsBackground = true ; mythread.Start("PornVideo" ); Console.ReadKey(); } } }
线程的优先级 线程由操作系统调度,一个CPU线程同一时间只能做一件事情(运行一个线程中的计算任务),当有很多线程需要CPU去执行的时候,线程调度器会根据线程的优先级去判断先去执行哪一个线程,如果优先级相同的话,就使用一个循环调度规则,逐个执行每个线程。
在Thread类中,可以设置Priority属性,以影响线程的基本优先级,Priority属性是一个ThreadPriority枚举定义的一个值。定义的级别有Highest,AboveNormal,BelowNormal,和Lowest。
控制线程
获取线程的状态(Running还是Unstarted),当我们通过调用Thread对象的Start方法,可以创建线程,但是调用了Start方法之后,新线程不是马上进入Running状态,而是处于Unstarted状态,只有当操作系统的线程调度器选择了要运行的线程,这个线程的状态才会修改为Running状态。我们使用Thread.Sleep()
方法可以让当前线程休眠进入WaitSleepJoin
状态。
使用Thread对象的Abort()
方法可以停止线程。调用这个方法,会在要终止的线程中抛出一个ThreadAbortException
类型的异常,我们可以try catch这个异常,然后在结束线程前做一些清理的工作。
如果需要等待线程的结束,可以调用Thread对象的Join
方法,表示把Thread加入进来,停止当前线程,并把它设置为WaitSleepJion状态,直到加入的线程完成为止。
6. 线程开启方式3-线程池 创建线程需要时间。如果有不同的小任务要完成,就可以事先创建许多线程,在应完成这些任务时发出请求。这个线程数最好在需要更多的线程时增加,在需要释放资源时减少。
不需要自己创建线程池,系统已经有一个ThreadPool类管理线程。这个类会在需要时增减池中的线程数,直到达到最大的线程数。池中最大线程数是可配置的。在双核CPU中,默认设置为1023个工作线程和1000个I/O线程。
CPU的线程数指的是当前CPU能够并发执行的最大线程数(比如16线程、32线程等),线程池中的线程是一个缓存的概念,它们通过调度、抢占轮番执行,所以一个线程池能容纳几千个线程
可以指定在创建线程池时应立即启动的最小线程数,以及线程池中可用的最大线程数。
如果有更多的作业要处理,线程池中线程的个数也到了极限,最新的作业就要排队,且必须等待线程完成其任务。
线程池示例 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 using System;using System.Threading;namespace ThreadPoolTest { class ThreadPoolTest1 { static void Main (string [] args ) { int nWorkerThreads; int nCompletionFortThreads; ThreadPool.GetMaxThreads(out nWorkerThreads, out nCompletionFortThreads); Console.WriteLine("Max Worker Threads:" + nWorkerThreads + " I/O Completion Threads:" + nCompletionFortThreads); for (int i = 0 ; i < 5 ; i++) { ThreadPool.QueueUserWorkItem(JobForAThread); } Thread.Sleep(3000 ); Console.ReadKey(); } static void JobForAThread (object state ) { for (int i = 0 ; i < 3 ; i++) { Console.WriteLine("Loop " + i + " is running in pooled thread " + Thread.CurrentThread.ManagedThreadId); Thread.Sleep(300 ); } } } }
示例应用程序首先要读取工作线程和IO线程的最大线程数,把这些信息写入控制台中,接着在for循环中,调用ThreadPool.QueueUserWorkItem
方法,传递一个WaitCallBack类型的委托,把JobForAThread方法赋予线程池中的线程。线程池收到这个请求后,就会从池中选择一个线程来调用该方法。如果线程池还没有运行,就会创建一个线程池,并启动第一个线程。如果线程池已经在运行,且有一个空闲线程来完成该任务,就把该作业传递给这个线程。
使用线程池需要注意的事项
线程池中所有的线程都是后台线程,如果进程的所有前台线程都结束了,所有的后台线程就会停止。不能把入池的线程改为前台线程。
不能给入池的线程设置优先级或名称。
入池的线程只能用于时间较短的任务,如果线程要一直运行(如Word的拼写检查器线程),就应使用Thread类创建一个线程。
7. 线程开启方式4-任务 任务 在.Net4新的命名空间System.Threading.Tasks包含的类抽象出了线程的功能,在后台使用的ThreadPool进行管理的。任务表示应完成某个单元的工作。这个工作可以在单独的线程中运行,也可以以同步方式启动一个任务。
任务也是异步编程中的一种实现方式,而且是现在微软首推的方式。
启动任务 启动任务的三种方式
1 2 3 4 5 6 TaskFactory tf = new TaskFactory(); Task t1 = tf.StartNew(TaskMethod); Task t2 = TaskFactory.StartNew(TaskMethod); Task t3 = new Task(TaskMethod); t3.Start();