C军

不玩博客了!

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

  操作系统通过线程对程序的执行进行管理,当操作系统运行一个程序的时候,首先,操作系统将为这个准备运行的程序分配一个进程,以管理这个程序所需要的各种资源。在这些资源之中,会包含一个称为主线程的线程数据结构,用来管理这个程序的执行状态。

  在Windows操作系统下,线程的的数据结构包含以下内容:

  1、线程的核心对象:主要包含线程当前的寄存器状态,当操作系统调度这个线程开始运行的时候,寄存器的状态将被加载到CPU中,重新构建线程的执行环境,当线程被调度出来的时候,最后的寄存器状态被重新保存到这里,已备下一次执行的时候使用。
  2、线程环境块(Thread Environment Block,TED):是一块用户模式下的内存,包含线程的异常处理链的头部。另外,线程的局部存储数据(Thread Local Storage Data)也存在这里。
  3、用户模式的堆栈:用户程序的局部变量和参数传递所使用的堆栈,默认情况下,Windows将会被分配1M的空间用于用户模式堆栈。
  4、内核模式堆栈:用于访问操作系统时使用的堆栈。

  在抢先式多任务的环境下,在一个特定的时间,CPU将一个线程调度进CPU中执行,这个线程最多将会运行一个时间片的时间长度,当时间片到期之后,操作系统将这个线程调度出CPU,将另外一个线程调度进CPU,我们通常称这种操作为上下文切换。

  在每一次的上下文切换时,Windows将执行下面的步骤:

  • 将当前的CPU寄存器的值保存到当前运行的线程数据结构中,即其中的线程核心对象中。
  • 选中下一个准备运行的线程,如果这个线程处于不同的进程中,那么,还必须首先切换虚拟地址空间。
  • 加载准备运行线程的CPU寄存器状态到CPU中。

  公共语言运行时CLR(Common Language Runtime)是.Net程序运行的环境,它负责资源管理,并保证应用和底层操作系统之间必要的分离。

  在.Net环境下,CLR中的线程需要通过操作系统的线程完成实际的工作,目前情况下,.Net直接将CLR中的线程映射到操作系统的线程进行处理和调度,所以,我们每创建一个线程将会消耗1M以上的内存空间。但未来CLR中的线程并不一定与操作系统中的线程完全对应。通过创建CLR环境下的逻辑线程,我们可能创建更加节省资源的线程,使得大量的CLR线程可以工作在少量的操作系统线程之上。

一、线程的定义

  在单CPU系统的一个单位时间(time slice)内,CPU只能运行单个线程,运行顺序取决于线程的优先级别。如果在单位时间内线程未能完成执行,系统就会把线程的状态信息保持到线程的本地存储器(TLS)中,以便下次执行时恢复执行。而多线程只是系统带来的一个假象,它在多个单位时间内进行多个线程的切换,因为切换频密而且单位时间非常短暂,所以多线程被视作同时运行。

  适当使用多线程能提高系统的性能,比如:在系统请求大容量的数据时使用多线程,把数据输出工作交给异步线程,使主线程保持其稳定性去处理其他问题。但需要注意一点,因为CPU需要花费不少的时间在线程的切换上,所以过多地使用多线程反而会导致性能的下降。

  1、System.Threading 命名空间中的常用类

  在System.Threading命名空间内提供多个方法来构建多线程应用程序,其中ThreadPool与Thread是多线程开发中最常用到的,在.NET中专门设定了一个CLR线程池专门用于管理线程的运行,这个CLR线程池正是通过ThreadPool类来管理,而Thread是管理线程的最直接方式。

说明
AutoResetEvent 通知正在等待的线程已发生事件
ManualResetEvent 通知正在等待的线程已发生事件
Interlocked 为多个线程共享的变量提供原子操作
Monitor 提供同步对对象的访问的机制
Mutex 一个同步基元,也可用于进程间同步
Thread 创建并控制线程,设置其优先级并获取其状态
ThreadPool  提供一个线程池,该线程池可用于发送工作项、处理异步 I/O、代表其他线程等待以及处理计时器
WaitHandle 封装等待对共享资源的独占访问的操作系统特定的对象
ReadWriterLock 读写锁
Semaphore 控制线程的访问数量

 

二、线程的优先级

  理解有误,示例并不正确;

  为了方便线程的管理,线程有个优先级,优先级用于决定哪个线程优先执行,在Thread对象中就是Priority属性。

  优先级由低到高分别是:

优先级 说明
Lowest 可以将 Thread 安排在具有任何其他优先级的线程之后
BelowNormal 可以将 Thread 安排在具有 Normal 优先级的线程之后,在具有 Lowest 优先级的线程之前
Normal 默认值。可以将 Thread 安排在具有 AboveNormal 优先级的线程之后,在具有 BelowNormal 优先级的线程之前
AboveNormal 可以将 Thread 安排在具有 Highest 优先级的线程之后,在具有 Normal 优先级的线程之前
Highest 可以将 Thread 安排在具有任何其他优先级的线程之前

  先来看一个优先级的示例:

    class Program
    {
        static void Main(string[] args)
        {
            //新建3个线程并设定各自的优先级
            Thread t1 = new Thread(Run);
            t1.Priority = ThreadPriority.Lowest;
            Thread t2 = new Thread(Run);
            t2.Priority = ThreadPriority.Normal;
            Thread t3 = new Thread(Run);
            t3.Priority = ThreadPriority.Highest;
            //由低到高优先级的顺序依次调用
            t1.Start();
            t2.Start();
            t3.Start();
            Console.ReadKey();
        }

        public static void Run()
        {
            Console.WriteLine("我的优先级是:" + Thread.CurrentThread.Priority);
        }
    }

  来看输出:

  

  留意到线程是按照优先级的顺序执行的。

三、常用属性

常用属性 说明
CurrentThread 获取当前正在运行的线程
IsAlive 获取一个值,该值指示当前线程的执行状态
IsBackground 获取或设置一个值,该值指示某个线程是否为后台线程, 后台线程会随前台线程的关闭而退出
IsThreadPoolThread 获取一个值,该值指示线程是否属于托管线程池
ManagedThreadId 获取当前托管线程的唯一标识符
Name 获取或设置线程的名称
Priority 获取或设置一个值,该值指示线程的调度优先级
ThreadState 获取一个值,该值包含当前线程的状态

  ManagedThreadId是确认线程的唯一标识符,程序在大部分情况下都是通过Thread.ManagedThreadId来辨别线程的。不能够通过Name,因为Name只是一个简单的属性,可以随便改,不能保证无重复。

  常用属性示例:

    class Program
    {
        static void Main(string[] args)
        {
            //新建3个线程并设定各自的优先级
            Thread t1 = new Thread(Run);
            t1.Priority = ThreadPriority.Normal;
            t1.Start();

            Console.ReadKey();
        }

        public static void Run()
        {
            Thread t1 = Thread.CurrentThread;   //静态属性,获取当前执行这行代码的线程
            Console.WriteLine("我的优先级是:" + t1.Priority);          
            Console.WriteLine("我是否还在执行:" + t1.IsAlive);
            Console.WriteLine("是否是后台线程:" + t1.IsBackground);
            Console.WriteLine("是否是线程池线程:" + t1.IsThreadPoolThread);
            Console.WriteLine("线程唯一标识符:" + t1.ManagedThreadId);
            Console.WriteLine("我的名称是:" + t1.Name);
            Console.WriteLine("我的状态是:" + t1.ThreadState);
        }
    }

  输出如下:

  

  1、前台线程与后台线程的区别

  我们看到上面有个属性叫后台线程,非后台线程就叫前台线程吧,Thread.Start()启动的线程默认为前台线程,启动程序时创建的主线程一定是前台线程。应用程序与必须等到所有的前台线程执行完毕才会卸载。而当IsBackground设置为true时,就是后台线程了,当主线程执行完毕后就直接卸载,不再理会后台线程是否执行完毕。

  前台与后台线程的设置必须在线程启动之前进行设置,线程启动之后就不能设置了。

  Thread创建的线程是前台线程,线程池中的是后台线程。

    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Run);
            t1.IsBackground = true;     //设为后台线程
            t1.Start();
            Console.WriteLine("不等你咯,后台线程!");

            //注意这里不要Console.Readxxx();,让控制台执行完毕就自动关闭
        }

        public static void Run()
        {
            Thread.Sleep(5000);
            Console.WriteLine("后台线程正在执行!");
        }
    }

  前台线程与后台线程的区别如下,上面的示例没法用图片来说明,简要说发生的情况。当t1设置为前台线程时,5秒后,控制台窗口才关闭。如果t1设置为后台线程,则窗口瞬间就关闭了。

  2、ThreadState的状态

  对于ThreadState的值有以下几种:

线程状态 说明
Aborted 线程已停止
AbortRequested 线程的Thread.Abort()方法已被调用,但是线程还未停止
Background 线程在后台执行,与属性Thread.IsBackground有关
Running 线程正在正常运行
Stopped 线程已经被停止
StopRequested 线程正在被要求停止
Suspended 线程已经被挂起(此状态下,可以通过调用Resume()方法重新运行)
SuspendRequested 线程正在要求被挂起,但是未来得及响应
Unstarted 未调用Thread.Start()开始线程的运行
WaitSleepJoin 线程因为调用了Wait(),Sleep()或Join()等方法处于封锁状态

  线程在以上几种状态的切换如下:

  刚刚创建的线程处于已经准备好运行,但是还没有运行的状态,称为Ready(准备)状态。在操作系统的调度之下,这个线程可以进入(Runing)运行状态。运行状态的线程可能因为时间片用完的缘故被操作系统切换出CPU,称为Suspended(暂停运行)状态,也可能在时间片还没有用完的情况下,因为等待其他优先级更高的任务,而转换到Blocked(阻塞)状态。在阻塞状态下的线程,随时可以因为再次调度而重新进入运行状态。线程还可能通过Sleep方法进入Sleep(睡眠)状态,当睡眠时间到期之后,可以再次被调度运行。处于运行状态的线程还可能被主动终止执行,直接结束;也可能因为任务已经完成,被操作系统正常结束。

四、方法

方法 说明
Abort 终止线程
GetDomain 当前线程运行在的应用程序域
GetDomainID 唯一的应用程序域标识符
Interrupt 中断处于 WaitSleepJoin 线程状态的线程
Join 阻塞调用线程,直到某个线程终止时为止
ResetAbort 取消为当前线程请求的 Abort
Sleep 将当前线程阻塞指定的毫秒数
SpinWait 导致线程等待由 iterations 参数定义的时间量
Start  启动线程以按计划执行

1、Join串行执行

Join,串行执行,相当于ajax里面的async:false

    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Run);
            t1.Name = "t1";
            t1.Start();
            t1.Join();      //等待t1执行完之后,主线程再执行,线程间的关系为串行,非并行
            Console.WriteLine("主线程执行这了么?");
            Console.ReadKey();
        }

        public static void Run()
        {
            Console.WriteLine("线程" + Thread.CurrentThread.Name + "开始执行!");
            Thread.Sleep(5000);
            Console.WriteLine("线程" + Thread.CurrentThread.Name + "执行完毕!");
        }
    }

  输出:

  

  2、Interrupt 与 Abort

  Interrupt和Abort:这两个关键字都是用来强制终止线程,不过两者还是有区别的。

        1、Interrupt:  抛出的是 ThreadInterruptedException 异常。

     Abort:  抛出的是  ThreadAbortException 异常。

        2、Interrupt:如果终止工作线程,只能管到一次,工作线程的下一次sleep就管不到了,相当于一个contine操作。如果线程正在sleep状态,则通过Interrypt跳过一次此状态也能够达到唤醒效果。

     Abort:这个就是相当于一个break操作,工作线程彻底停止掉。 当然,你也已在catch(ThreadAbortException ex){...} 中调用Thread.ResetAbort()取消终止。

    class Program
    {
        static void Main(string[] args)
        {
            Thread t1 = new Thread(Run);
            t1.Start();
            //当Interrup时,线程已进入for循环,中断第一次之后,第二次循环无法再停止
            t1.Interrupt();

            t1.Join();
            Console.WriteLine("============================================================");

            Thread t2 = new Thread(Run);
            t2.Start();
            //停止1秒的目的是为了让线程t2开始,否则t2都没开始就直接中止了
            Thread.Sleep(1000);
            //直接终止掉线程,线程被终止,自然无法输出什么!
            t2.Abort();

            Console.ReadKey();
        }

        static void Run()
        {
            for (int i = 1; i <= 5; i++)
            {
                try
                {
                    //连续睡眠5次
                    Thread.Sleep(2000);
                    Console.WriteLine("" + i + "次Sleep!");
                }
                catch (Exception e)
                {
                    Console.WriteLine("" + i + "次Sleep被中断!" + "   " + e.Message);
                }
            }
        }
    }  

  输出:

  

  3、Suspend 与 Resume (慎用)

  Thread.Suspend()与 Thread.Resume()是在Framework1.0 就已经存在的老方法了,它们分别可以挂起、恢复线程。但在Framework2.0中就已经明确排斥这两个方法。这是因为一旦某个线程占用了已有的资源,再使用Suspend()使线程长期处于挂起状态,当在其他线程调用这些资源的时候就会引起死锁!所以在没有必要的情况下应该避免使用这两个方法。在MSDN中,这两个方法也已被标记为已过时。

五、ThreadStart委托

  ThreadStart所生成并不受线程池管理。

  通过ThreadStart委托启动线程:

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("主线程Id是:" + Thread.CurrentThread.ManagedThreadId);
            Message message = new Message();
            Thread thread = new Thread(new ThreadStart(message.ShowMessage));
            thread.Start();
            Console.WriteLine("正在做某事......");
            Console.WriteLine("主线程工作完成!");

            Console.ReadKey();
        }

        public class Message
        {
            public void ShowMessage()
            {
                string message = string.Format("异步线程Id是:{0}", Thread.CurrentThread.ManagedThreadId);
                Console.WriteLine(message);
                for (int i = 0; i < 10; i++)
                {
                    Thread.Sleep(300);
                    Console.WriteLine("异步线程当前循环执行到" + i);
                }
            }
        }
    }

  输出:

  

六、ParameterizedThreadStart委托

  ParameterizeThreadStart委托于ThreadStart委托非常相似,但ParameterizedThreadStart委托是面向带参数方法的。注意ParameterizedThreadStart对应方法的参数为object。

    class Person
    {
        public Person(string name, int age){ this.Name = name;this.Age = age; }
        public string Name { get;  set; }
        public int Age { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            //整数作为参数
            for (int i = 0; i < 2; i++)
            {
                Thread t = new Thread(new ParameterizedThreadStart(Run));
                t.Start(i);
            }
            Console.WriteLine("主线程执行完毕!");

            //自定义类型作为参数
            Person p1 = new Person("关羽", 22);
            Person p2 = new Person("张飞", 21);
            Thread t1 = new Thread(new ParameterizedThreadStart(RunP));
            t1.Start(p1);
            Thread t2 = new Thread(new ParameterizedThreadStart(RunP));
            t2.Start(p2);

            Console.ReadKey();
        }

        public static void Run(object i)
        {
            Thread.Sleep(50);
            Console.WriteLine("线程传进来的参数是:" + i.ToString());
        }

        public static void RunP(object o)
        {
            Thread.Sleep(50);
            Person p = o as Person;
            Console.WriteLine(p.Name + p.Age);
        }
    }

  输出:

  

七、TimerCallback委托

  TimerCallback委托专门用于定时器的操作,这个委托允许我们定义一个定时任务,在指定的间隔之后重复调用。实际的类型与ParameterizedThreadStart委托是一样的。

  Timer类的构造函数定义如下:

  public Timmer(TimerCallback callback,Object state,long dueTime,long period)
  • Callback表示一个时间到达时执行的委托,这个委托代表的方法必须符合委托TimerCallback的定义。
  • State表示当调用这个定时器委托时传递的参数。
  • dutTime表示从创建定时器到第一次调用时延迟的时间,以毫秒为单位。
  • Period表示定时器开始之后,每次调用之间的时间间隔,以毫秒为单位。

  示例,使用TimerCallback每隔一秒钟输出一次时间:

    class Program
    {
        static void Main(string[] args)
        {
            System.Threading.Timer clock = new System.Threading.Timer(ConsoleApplication1.Program.ShowTime, null, 0, 1000);

            Console.ReadKey();
        }

        public static void ShowTime(object userData)
        {
            Console.WriteLine(DateTime.Now.ToString());
        }
    }

  输出如下:

  

posted on 2013-03-26 23:57  逆心  阅读(5139)  评论(5编辑  收藏  举报