<转载>C#与.NET对多线程的处理

C#和.NET基类为开发多线程应用程序所提供的支持。我们将简要介绍Thread和ThreadPool类以及各种线程支持,再用两 介示例来说明线程的规则。然后论述线程同步时会出现的问题。
    .如何开始一个线程
    .提供线程的优先级
    .通过同步控制对对象的访问
    1、线程是程序中独立的指令流。主要是给应用程序提供了多个执行线程,应用程序可以有任意多个线程。每次创建一个新执行线程时,都需要指定从哪个方法开始执行。应用程序中的第一个线程总是Main()方法,因为第一个线程是由.NET运行库开始执行的,Main()方法是.NET运行库选择的第一个方法。后续的线程由应用程序在内部启动,即应用程序可以选择启动哪个线程。
    2、实际上,一个处理器在某一时刻只能处理一个任务。如果有一个多处理器系统,理论上它可以同时执行多个指令,但大多数人用的是单处理器计算机,这种情况不可能发生(双核),而实际上,Windows操作系统表面上可以同时处理多个任务,这个任务称为抢先式多任务处理(pre-emptive multitasking)。
    所谓抢先式多任务处理,是指Windows在某个进程中选择一个线程,该线程运行一小段时间。Microsoft没有说明这段时间有多长,因为为了获得最好的性能,Windows有一个内部操作系统参数来控制这个时间值。但在运行Windows应用程序时,用户不需要知道它。从我们这个角度来看,这个时间非常短。这段时间称为线程的时间片(time slice),过了这段时间,操作系统就收回控制权,选择下一个被分配了时间片的线程,这个时间片非常短,我们可以认为许多事件是同时发生的。
    3、线程的处理
    C#中线程是用Thread类来处理的。一个Thread实例表示一个线程,即执行序列。
因为线程入口点不能带任何参数,所以必须采用其他的方式,给方法传送需要的信息。最明显的方式是使用这个方法所属类的成员字段。除了不能带参数之外,该方法还不能返回信息。(考虑:返回值应返回到什么地方?如果这个方法有返回值,运行它的线程就会终止,所以根本接收不到返回值,也不可能把值返回给调用它的线程,因为那时线程在忙于干其他事。)
    .Net 2.0引入了新特性,它们改变了启动线程的方式。.NET的最新版本引入了匿名方法,现在不必创建一个独立的方法,而可以把方法的代码块直接放在委托声明中。这个新增特性显著改变了启动线程的方式。
    如:
    void ChangeColorDepth()
    {
       Thread depthChangeThread=new Thread(delegate(){ //processing to change color depth of image });
       depthChangeThread.Name="DepthChange Thread";
       depthChangeThread.Start();
    }
    启动一个线程后,还可以挂起,恢复或中止它。挂起一个线程就是暂停线程或让它进入睡眠状态,可以再次运行。如果线程被中止,就是停止运行。Windows会永久地删除该线程的所有数据,所以该线程不能重新启动。如果后续的处理依赖于另一个已经中止的线程,可以调用Join()方法,等待线程中止。
    如果主线程要在它自己的线程上执行某些操作,该怎么办?此时需要一个线程对象的引用来表示它自己的线程。使用Thread类的静态属性CurrentThread,就可以获得这样一个引用:
Thread myOwnThread=Thread.CurrentThread;
    线程实际上是一个不太好处理的类,因为即使在没有实例化其他线程以前,也总是会有一个线程:目前正在执行的线程。因此处理这个类与其他类有两个区别:
    .可以实例化一个线程对象,它表示一个正在运行的线程,其实例成员应用于正在运行的线程。
    .可以调用任意个静态方法。这些方法一般会应用到实际调用它们的线程中。
 
    观察下面的示例:
    class EntryPoint
    {
       static int interval;
       static void Main()
       {
          Console.Write("Interval to display results at?>");
          interval=int.Parse(Console.ReadLine());
          Thread thisThread=Thread.CurrentThread;
          thisThread.Name="Main Thread";
         
          ThreadStart workerStart=new ThreadStart(StartMethod);
          Thread workerThread=new Thread(workerStart);
          workThread.Name="Worker";
          workerThread.Start();
 
          DisplayNumber();
          Console.WriteLine("Main Thread Finished");
         
          Console.ReadLine();         
       }
       static void DisplayNumber()
       {
          Thread thisThread=Trhead.CurrentThread;
          string name=thisThread.Name;
          Console.WriteLine("Starting thread:"+name);
          Console.WriteLine(name+":Current Culture="+thisThread.CurrentCulture);
          for(int i=1;i<-8*interval;i++)
          {
             if(i%interval==0)
                 Console.WriteLine(name+":count hase reached "+i);
          }
       }
       static void StartMethod()
       {
          DisplayNumbers();
          Console.WriteLine("Worker Thread Finished");
       }
    }

    这些方法都是类EntryPoint的静态方法。两 个累加过程是完全独立,因为DisplayNumbers()方法中用于累加数字的变量i是一个局部变量,局部变量只能在定义它们的方法中使用,也只有执行该方法的线程中是可见的。如果另一个线程开始执行这个方法,该线程就会获得该局部变量的副本。运行这段代码,给interval选择一个相对小的值100,得到如下结果:
    ThreadPlayaround
    Interval to display results at?>100
    Starting thread: Main Thread
    Main Thread:Current Culture=en-US
    Main Tread:count has reached 100
    ......
 
    Main Tread Finished
    Starting thread:Worker
    Worker:Current Culture=en-US
    Work:count has reached 100
    ......
 
    对于并行的线程而言,两个线程的执行都非常成功。启动主线程,累加到800之后完成执行,然后启动工作线程,执行累加过程。
    此处的问题是启动线程是一个主进程,在实例化一个新线程后,主线程会遇到下面的代码:
               workerThread.Start();
    它调用Thread。Start(),告诉Windows新线程已经准备启动,然后即时返回。在累加到800时,Windows就启动新线程,这意味着给该线程分配各种资源,执行各种检查。到新线程启动时,主线程已经完成了任务。
 
    4、线程的优先级
    如果在应用程序中有多个线程在运行,但一些线程比另一些线程重要,该怎么办?在这种情况下,可以在一个进程中为不同的线程指定不同的优先级。
    线程优先级可以定义为ThreadPriority枚举的值,即Highest、AboveNormal、Normal、BelowNormal和Lower。
 
    5、同步
    使用线程一个重要方面是同步访问多个线程访问的任何变量。所谓同步,是指在某一时刻只有一个线程可以访问变量。如果不能确保对变量的访问是同步的,就会产生错误。
    (1)同步的含义
    同步问题的产生,是由于在C#源代码中,大多数情况下看起来是一条语句,但在最后编译好的汇编语言机器码中会被翻译为许多条语句。看看下面这个语句:
          message+=",there";   //message variable si a string that contains "Hello"
    在语法上是一条语句,但在执行时,实际上它涉及到许多操作。需要分配内存,以存储更长的新字符串,需要设置变量message,使之指向新的内存,需要复制实际文本等。如果在这些语句完成以前,另一个线程访问这个变量,那么是旧值还是新值呢?
    C#为同步访问变量提供了一种非常简单的方式,即使用C#语言的关键字lock,其用法如下所示:
    lock(x)
    {
       DoSomething();
    }
    lock语句把变量放在圆括号中,以包装对象,称为独占锁或排它锁。当执行带有lock关键字的复合语句时,独占锁会保留下来。当变量被包装在独占锁中时,这个线程就会失去其时间片。如果下一个获得时间片的线程试图访问变量x时,就会被拒绝。Windows会让其他线程处于睡眠状态,直到解除了独占锁为止。
    (2)同步问题
    同步线程在多线程应用程序中非常重要。但是,很容易出现微妙且难以察觉的问题,特别是死锁(dead lock)和竞态条件(Race conditions)。
    a、不要滥用同步   会降低性能。原因一,在对象上放置和解除独占锁会带来某些系统开销。原因二、同步使用得越多,等待释放对象的线程就赵多。
    b、死锁      死锁是一个错误,在两个线程都需要访问被互锁的资源时就会发生死锁。以两个线程以相同的顺序在对象上声明加锁,就可以避免发生死锁。
    c、竞态条件    竞态条件比死锁更微妙。它很少中断进程的执行,但可能导致数据损坏。很难给竞态下一个准确的定义。
   
    6、使用ThreadPool创建线程
    前面介绍了如何通过Thread类,一次使用一个线程,来创建和删除线程。以这种方式建立和删除线程是很昂贵的。所以,CLR包含一个内置的线程池,供应用程序使用。这个线程池可以通过ThreadPool类访问。
    ThreadPool类会在线程的托管池中重用已有的线程。使用完线程后,线程就会返回线程池,供以后使用。每个CPU ThreadPool有25个可用的线程。
   
    确定用ThreadPool类还是Thread类创建线程时,考虑如下问题:
    在达到如下目标时,应使用ThreadPool类:
    .要以最简单的方式创建和删除线程
    .应用程序使用线程的性能要优先考虑
    在达到如下目标时,应使用Thread类:
    .要控制所创建线程的优先级
    .希望所使用的线程维护其标识,该标识要与线程一起进行各种操作,经过许多不同的时间段
    .所使用的线程的寿命较长
 
    下面的例子演示了使用ThreadPool类创建一个线程的过程。首先创建一个控制台应用程序。该应用程序的代码如下:
 
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
 
namespace ConsoleApplication1
{
class Program
{
    static int interval;
    static void Main(string[] args)
    {
      Console.Write("Interval to display results at ?>");
      interval=int.Parse(Console.ReadLine());
 
      ThreadPool.QueueUserWorkItem(new WaitCallback(StartMethod));
      Thread.Sleep(100);
      ThreadPool.QueueUserWorkItem(new WaitCallback(StartMethod));
      Console.ReadLine();
    }

    static void StartMethod(Object stateInfo)
    {
      DisplayNumbers("Tread "+dateTime.Now.Millisecond.ToString());
      Console.WriteLine("Tread Finished");     
    }
 
    static void DisplayNumbers(string GivenThreadName)
    {
      Console.WriteLine("Starting thread: "+GivenTrheadName);
      for(int i=1;i<=8*interval;i++)
      {
        if(i%interval==0)
        {
          Console.WriteLine("Count has reached "+i);
          Thread.Sleep(1000);
        }
      }
    }
}
}
 
    这个线程的创建不使用Thread的实例,而只需调用ThreadPool。QueueUserWorkItem(),调用这个方法有两种方式,前面是进行这个调用的一个变体。下面是该方法的另一种方法:
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartMethod));
    还可以使用下面的结构:
        ThreadPool.QueueUserWorkItem(StartMethod);
    使用WaitCallback委托时,还可以传送一个参数,如下面的代码示例所示:
        ThreadPool.QueueUserWorkItem(new WaitCallback(StartMethod),“first Thread”);
    传送一个字符串,接着在StartMethod方法中使用它,如下所示:
    static void StartMehod(Object stateInfo)
    {
      DisplayNumbers("Thread"+stateInfo.ToString());
      Console.WriteLine("Thread Finished");
    }
 
    结果:
    Interval to display results at ?>100
    Starting thread: Thread First Thread

    还可以使用Thread对象获得线程的许多属性。

原文地址:https://www.cnblogs.com/ChangTan/p/2050389.html