在使用线程的时候,我们经常会见到线程优先级的问题,那么什么是线程优先级呢,线程优先级决定了该线程可占用多少的CPU时间。在C#程序中,可以对线程设定五个不同的优先级,从高到低依次是Highest、AboveNormal、Normal、BelowNormal和Lowest,在创建线程时,如果不指定其优先级,则系统默认为Normal。假如想让一些重要的线程优先执行,可以使用下面的方法为其赋予较高的优先级。
<span style="font-size:18px;">Thread t = new Thread(new ThreadStart(thread_method)); t.priority = ThreadPriority.AboveNormal;</span>
注意,当把某线程的优先级设为Highest时,系统上正在运行的其他线程都会终止,所以使用这个优先级是要特别小心。
使用线程的时候会碰到一个非常经典的问题,那就是线程同步。所谓同步,是指多个线程之间存在先后执行顺序的关联关系。如果一个线程必须在另一个线程完成某个工作后才能执行,则必须考虑如何让其保持同步,以确保在系统运行多个线程而不会出现逻辑错误。比如下面的例子是多线程问题中比较经典的售票问题:
<span style="font-size:18px;">namespace TestThread { class Program { static void Main(string[] args) { Console.WriteLine("Starting..."); Thread thread1 = new Thread(run); Thread thread2 = new Thread(run); Thread thread3 = new Thread(run); thread1.Start(); thread2.Start(); thread3.Start(); Console.ReadKey(); } private static int count = 10;//火车票的数量 public static void run() { for (int i = 0; i < 100; i++) { if (count > 0) {//表示还有票 try { Thread.Sleep(300); } catch (Exception e) { // TODO Auto-generated catch block Console.WriteLine(e.StackTrace); } Console.WriteLine("现在还剩{0},已经卖出{1}",--count,10-count); } } } } }</span>输出为:
我们从输出中可以看到,三个线程分别执行了三次购买,从10变成了7,当300ms过后,再输出,我们发现了逻辑错误,这就是因为线程没有同步,导致数据共用,出现了逻辑错误。这也叫做竞争条件。那么我们怎么解决这个错误,其实很简单,在共用的数据上加上lock关键字,其逻辑是当已有线程操作count对象时,所有其他线程必须等待直到当前线程完成操作。如下所示:
<span style="font-size:18px;">namespace TestThread { class Program { static void Main(string[] args) { Console.WriteLine("Starting..."); Ticket ticket = new Ticket(); Thread thread1 = new Thread(ticket.run); Thread thread2 = new Thread(ticket.run); Thread thread3 = new Thread(ticket.run); //Thread thread = new Thread(PrintEvenNumber); //thread.Start(); //thread.Join(); //PrintOddNumber(); thread1.Start(); thread2.Start(); thread3.Start(); Console.ReadKey(); } class Ticket { private int count = 10;//火车票的数量 public void run() { int num = count; for (int i = 0; i < 100; i++) { lock (this) { if (count > 0) {//表示还有票 try { Thread.Sleep(300); --count; num = 10 - count; } catch (Exception e) { // TODO Auto-generated catch block Console.WriteLine(e.StackTrace); } Console.WriteLine("现在还剩{0},已经卖出{1}", count, num); } } } } } } }</span>此时,输出为:
输出正确。
接下来来介绍向线程传递参数的几种方法,直接上代码:
<span style="font-size:18px;">namespace TestThread2 { class Program { static void Main(string[] args) { var sample = new ThreadSample(10); var threadOne = new Thread(sample.CountNumbers); threadOne.Name = "ThreadOne"; threadOne.Start(); threadOne.Join(); Console.WriteLine("------------------------------"); var threadTwo = new Thread(Count); threadTwo.Name = "ThreadTwo"; threadTwo.Start(8); threadTwo.Join(); Console.WriteLine("-------------------------------"); var threadThree = new Thread(() => CountNumbers(12)); threadThree.Name = "ThreadThree"; threadThree.Start(); threadThree.Join(); int i = 10;//特别注意 var threadFour = new Thread(() => PrintNumber(i)); i = 4; var threadFive = new Thread(() => PrintNumber(i)); threadFour.Start(); threadFive.Start(); Console.ReadKey(); } static void Count(object iterations) { CountNumbers((int)iterations); } static void CountNumbers(int iterations) { for (int i = 1; i<=iterations;i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i); } } static void PrintNumber(int number) { Console.WriteLine(number); } class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iterations = iterations; } public void CountNumbers() { for (int i =1; i<=_iterations;i++) { Thread.Sleep(TimeSpan.FromSeconds(0.5)); Console.WriteLine("{0} prints {1}", Thread.CurrentThread.Name, i); } } } } }</span>输出为:
线程使用另一个类中的方法以及静态方法比较简单,不再讲解,可以查看代码和输出进行理解,但是需要注意,当方法需要参数时,必须在start()方法中传递参数。
线程三和线程四都使用了lambda表达式,lambda表达式定义了一个不属于任何类的方法。我们创建了一个方法,该方法使用需要的参数调用了另一个方法,并在另一个方法中运行该方法。当启动threadThree线程时,打印出了12个数字,这就是我们通过lambda表达式传递的数字。
使用lambda表达式引用另一个C#对象的方法被称为闭包,当在lambda表达式中使用任何局部变量时,C#会生成一个类,并将该变量作为该类的一个属性。所以,线程四和线程五实际上与线程一一样,但是我们没有定义该类,因为C#编译器会自动帮我们实现。
这会导致几个问题,例如,如果在多个lambda表达式使用相同的变量,它们会共享该变量值。在上个例子中,当启动threadFour和threadFive线程时,它们都会打印4,因为在这两个线程启动之前变量被修改为4.