zoukankan      html  css  js  c++  java
  • <转载>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对象获得线程的许多属性。

  • 相关阅读:
    jsADS的库(待更新)
    javascript设计模式工厂方法模式
    jQuery星级评价
    邮政编码联动地址
    ADS图像缩放与剪裁(只是完成了前台的功能,传送数据到后台的功能待完成)
    ie6png透明解决(ietester的ie6有问题,原生ie6是没问题的)
    javascript设计模式桥接模式
    每一个人都应该学会执着
    防止电脑被攻击
    获取用户的IP地址的三个属性的区别
  • 原文地址:https://www.cnblogs.com/ChangTan/p/2050389.html
Copyright © 2011-2022 走看看