关于C#线程的全面解析
作者:老虎中的小白Gentle
线程的作用和意义
线程 被定义为程序的执行路径。每个线程都定义了一个独特的控制流。如果您的应用程序涉及到复杂的和耗时的操作,那么设置不同的线程执行路径往往是有益的,每个线程执行特定的工作。
线程是轻量级进程。一个使用线程的常见实例是现代操作系统中并行编程的实现。使用线程节省了 CPU 周期的浪费,同时提高了应用程序的效率。
到目前为止我们编写的程序是一个单线程作为应用程序的运行实例的单一的过程运行的。但是,这样子应用程序同时只能执行一个任务。为了同时执行多个任务,它可以被划分为更小的线程。
线程生命周期
线程生命周期开始于 System.Threading.Thread 类的对象被创建时,结束于线程被终止或完成执行时。
下面列出了线程生命周期中的各种状态:
- 未启动状态:当线程实例被创建但 Start 方法未被调用时的状况。
- 就绪状态:当线程准备好运行并等待 CPU 周期时的状况。
- 不可运行状态:下面的几种情况下线程是不可运行的:
- 已经调用 Sleep 方法
- 已经调用 Wait 方法
- 通过 I/O 操作阻塞
- 死亡状态:当线程已完成执行或已中止时的状况
C#创建线程
在C# 语言中使用线程时首先需要创建线程,在使用 Thread 类的构造方法创建其实例时,需要用到 ThreadStart 委托或者 ParameterizedThreadStart 委托创建 Thread 类的实例。ThreadStart 委托只能用于无返回值、无参数的方法,而ParameterizedThreadStart 委托则可以用于带参数的方法。
ThreadStar的方式创建
例子:
using System; using System.Threading; namespace MultithreadingApplication { class ThreadCreationProgram { //线程函数 public static void CallToChildThread() { Console.WriteLine("Child thread starts"); } static void Main(string[] args) { //创建ThreadStart的委托实例 ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine("In Main: Creating the Child thread"); //创建Thread类的实例 Thread childThread = new Thread(childref); childThread.Start(); //开始一个线程 Console.ReadKey(); } } }
运行结果:
ParameterizedThreadStart
例子:
using System; using System.Threading; namespace MultithreadingApplication { class Program { static void Main(string[] args) { //创建一个线程委托对象 ParameterizedThreadStart pts = new ParameterizedThreadStart(PrintEven); Console.WriteLine("In Main: Creating the Child thread"); // 创建一个线程对象 Thread childThread = new Thread(pts); childThread.Start(10); Console.ReadKey(); } //线程跑的函数 //打印0~n中的偶数 private static void PrintEven(Object n) { Console.WriteLine("Child thread started"); for(int i=0; i<=(int)n; i+=2) //类型转换 { Console.WriteLine(i); } } } }
运行结果:
C#让线程休眠一会
using System; using System.Threading; namespace MultithreadingApplication { class ThreadCreationProgram { public static void CallToChildThread() { Console.WriteLine("Child thread starts"); int sleepfor = 5000; Console.WriteLine("Child Thread Paused for {0} seconds", sleepfor / 1000); Thread.Sleep(sleepfor); //让线程暂停 单位毫秒 Console.WriteLine("Child thread resumes"); } static void Main(string[] args) { //创建一个线程的委托 ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine("In Main: Creating the Child thread"); //创建线程的实例 Thread childThread = new Thread(childref); childThread.Start(); Console.ReadKey(); } } }
运行结果:最后一行是5s后才打印出来的
C#销毁线程
/* 销毁线程 */ using System; using System.Threading; namespace MultithreadingApplication { class ThreadCreationProgram { //委托函数 public static void CallToChildThread() { try//引起异常的语句 { Console.WriteLine("Child thread starts"); for(int counter = 0; counter <= 10; counter++) { Thread.Sleep(500); Console.WriteLine(counter); } Console.WriteLine("Child Thread Completed"); } catch(ThreadAbortException e)//错误处理代码 { Console.WriteLine("Thread Abort Exception"); } finally //执行的语句 { Console.WriteLine("Could't catch the Thread Exception"); } } static void Main(string[] args) { //创建一个线程的委托实例 ThreadStart childref = new ThreadStart(CallToChildThread); Console.WriteLine("In Main: Creating the Child thread"); //创建一个线程对象 Thread childThread = new Thread(childref); childThread.Start(); //主线程休眠 Thread.Sleep(2000); Console.WriteLine("In Main:Aborting the Child thread"); //在调用此方法的线程上引发ThreadAbortException,以开始终止此线程的过程。 //调用此方法通常会终止线程 childThread.Abort(); Console.ReadKey(); } } }
运行结果:
C#线程优先级
在C#中线程的优先级使用线程的Priority属性设置即可,默认的优先级是Normal。在设置优先级后,优先级高的线程将优先执行。优先级的值通关ThreadPriority枚举类型来设置,从低到高分别为Lowest 、BelowNormal、Normal、 AboveNormal、 Highest。
例子:
using System; using System.Threading; namespace MultithreadingApplication { class Program { //奇数 public static void PrintOdd() { Console.WriteLine("List of odd numbers:"); for (int i = 1; i <= 100; i += 2) { Console.Write(i + " "); } Console.WriteLine(); } //偶数 public static void PrintEven() { Console.WriteLine("List of even numbers: "); for(int i = 0; i<=100; i+=2) { Console.Write(i + " "); } Console.WriteLine(); } static void Main(string[] args) { //创建线程的委托1 ThreadStart childref1 = new ThreadStart(PrintEven); Console.WriteLine("In Main: Creating the Child1 thread"); //创建线程1的实例 Thread childThread1 = new Thread(childref1); //设置打印偶数优先级为最低 childThread1.Priority = ThreadPriority.Lowest; //创建线程的委托2 ThreadStart childref2 = new ThreadStart(PrintOdd); Console.WriteLine("In Main: Creating the Child2 thread"); //创建线程2的实例 Thread childThread2 = new Thread(childref2); //设置打印奇数优先级为最高 childThread2.Priority = ThreadPriority.Highest; childThread1.Start();//偶数 低 childThread2.Start();//奇数 高 Console.ReadKey(); } } }
运行的结果:
第一次运行:
第二次:
第三次:
第四次:
小结:
从上面的运行效果可以看出,由于输岀奇数的线程的优先级高于输出偶数的线程,所以在输出结果中优先输出奇数的次数会更多。
此外,每次输出的结果也不是固定的。通过优先级是不能控制线程中的先后执行顺序的,只能是优先级高的线程优先执行的次数多而已。
线程状态控制的方法包括暂停线程 (Sleep)、中断线程 (Interrupt)、挂起线程 (Suspend)、唤醒线程 (Resume)、终止线程 (Abort)。
lock:给线程加锁,保证线程同步
sleep 方法能控制线程的暂停时间,从而改变多个线程之间的先后顺序,但每次调用线程的结果是随机的。线程同步的方法是将线程资源共享,允许控制每次执行一个线程,并交替执行每个线程。在 C# 语言中实现线程同步可以使用 lock 关键字和 Monitor 类、Mutex 类来解决。对于线程同步操作最简单的一种方式就是使用 lock 关键字,通过 lock 关键字能保证加锁的线程只有在执行完成后才能执行其他线程。
lock的语法如下
lock(object) { //临界区代码 }
这里 lock 后面通常是一个 Object 类型的值,也可以使用 this 关键字来表示。
最好是在 lock 中使用私有的非静态或负变量或私有的静态成员变量,即使用 Private 或 Private static 修饰的成员。
例如:
private Object obj = new Object(); lock (obj) { //临界区代码 }
一个更具体的实例
using System; using System.Threading; namespace MultithreadingApplication { class Program { //打印偶数 public void PrintEven() { //lock上锁保证执行完该线程才跑其他线程 lock(this) { for(int i=0; i<=10; i+=2) { //获取当前线程的名字 Console.WriteLine(Thread.CurrentThread.Name + "--" + i); } } } //打印奇数 public void PrintOdd() { lock (this) { for (int i = 1; i <= 10; i += 2) { Console.WriteLine(Thread.CurrentThread.Name + "--" + i); } } } static void Main(string[] args) { //因为下面要用到program类中的非静态函数,所以先创建该类对象 Program program = new Program(); //创建线程1的托管 ThreadStart ts1 = new ThreadStart(program.PrintOdd); //创建线程1 Thread t1 = new Thread(ts1); t1.Name = "打印奇数的线程"; //跑线程1 t1.Start(); ThreadStart ts2 = new ThreadStart(program.PrintEven); Thread t2 = new Thread(ts2); t2.Name = "打印偶数的线程"; t2.Start(); } } }
运行结果:
Monitor:锁定资源
和lock用法本质是一样的,使用Monitor类锁定资源的语法如下:
Monitor.Enter(object); try { //临界区代码 } finally { Monitor.Exit(object); }
这里的object与lock中的object一样。
具体例子
sing System; using System.Threading; namespace MultithreadingApplication { class Program { public void PrintEven() { //在指定对象上获取排它锁 Monitor.Enter(this); try//临界区代码 { for(int i=0; i<=10; i+=2) { Console.WriteLine(Thread.CurrentThread.Name + "--" + i); } } finally { //释放指定对象的排它锁 Monitor.Exit(this); } } public void PrintOdd() { Monitor.Enter(this); try { for(int i=1; i<=10; i+=2) { Console.WriteLine(Thread.CurrentThread.Name + "--" + i); } } finally { Monitor.Exit(this); } } static void Main(string[] args) { //下面创建委托对象时调用的是Program类的非静态方法, //所先创建一个program对象 Program program = new Program(); //实例化一个委托 ThreadStart ts1 = new ThreadStart(program.PrintOdd); //创建一个线程 Thread t1 = new Thread(ts1); //给线程名字赋值 t1.Name = "打印奇数的线程"; //开跑线程 t1.Start(); ThreadStart ts2 = new ThreadStart(program.PrintEven); Thread t2 = new Thread(ts2); t2.Name = "打印偶数的线程"; t2.Start(); } } }
运行结果:
Monitor 类的用法虽然比 lock 关键字复杂,但其能添加等待获得锁定的超时值,这样就不会无限期等待获得对象锁。使用 TryEnter() 方法可以给它传送一个超时值,决定等待获得对象锁的最长时间。
使用 TryEnter() 方法设置获得对象锁的时间的语法如下:
Monitor.TryEnter(object, 毫秒数 );
Mutex:互斥锁
Mutex类也是用于线程同步操作的类,当多个线程同时访问一个资源识保证只有一个线程访问资源。在Mutex类中,WaitOne()方法用于等待资源被释放,ReleaseMutex()方法用于释放资源。 WaitOne()方法在等待ReleMutex()方法执行后才会结束。
例子:
using System; using System.Threading; namespace MultithreadingApplication { class Program { //创建一个锁对象 private static Mutex mutex = new Mutex(); public static void PakingSpace(object num) { if(mutex.WaitOne())//等待释放资源,当前资源没调用时为true { try { Console.WriteLine("车牌号{0}的车驶入!", num); Thread.Sleep(1000);//线程休眠一秒 } finally { Console.WriteLine("车牌号{0}的车离开!", num); mutex.ReleaseMutex(); //释放锁资源 } } } static void Main(string[] args) { //创建一个委托带参数的 ParameterizedThreadStart ts = new ParameterizedThreadStart(PakingSpace); //创建一个线程 Thread t1 = new Thread(ts); t1.Start("A123456"); Thread t2 = new Thread(ts); t2.Start("B00000"); Console.ReadKey(); } } }
运行结果:
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。