zoukankan      html  css  js  c++  java
  • 多线程开发技术基础

    多线程开发技术基础

    多线程开发扫盲系列第二编:多线程开发技术基础

    1. 线程的创建、启动和停止   

    2. Windows操作系统线程调度策略   

    3. 向线程函数传送信息的方式   

    4. 线程统一取消模型   

    1.线程的创建、启动和停止

    在.NET应用程序中,线程由Thread类创建的对像代表。Thread类提供了许多属性和方法对线程进行控制
    Thread类拥有4个重载的构造函数,最常用的一个可接收一个ThreadStart类型的参数:public Thread(ThreadStart start)
    ThreadStart是一个委托,其定义如下:
    Public delegate void ThreadStart();
    从以上定义可知,在创建线程对像时必须传给它一个方法,此方法无参数且不返回从任何值。这个方法被称为”线程方法”,由于在面向对像程序中,方法本质上就是一个函数,因此人们习惯地又将”线程方法”称为”线程函数”,每个线程都对应着一个特定的线程函数,线程的执行体现着线程函数的执行
    线程函数可以是静态的,也可以是实例的
    Class MyThread
    {
    Public static void ThreadMethod1(){ ......}
    Public void ThreadMethod2(){......}
    }

    //将静态方法当作线程函数
    Thread th1=new Thread(MyThread.ThreadMethod1);
    //将实例方法当作线程函数
    MyThread obj=new MyThread();
    Thread th2=new Thread(obj.ThreadMethod2);
    当线程对像创建以后,调用它的Start方法启动线程
    Th1.Start();

    线程终止可以使用线程对像的Abort方法。
    这时线程对像自身会引发一个ThreadAboutException异常。代码如:
    namespace ThreadAbort
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("主线程开始");
                MyThread mythread = new MyThread();
                Thread th = new Thread(mythread.SomeLongTask);
               // th.IsBackground = true;
                th.Start();
                Thread.Sleep(300);
                Console.WriteLine("主线程调用abort方法提前终止辅助线程");
                th.Abort();
                Console.WriteLine("主线程结束");

                Console.ReadKey();
            }
        }

        class MyThread
        {
            public void SomeLongTask()
            {
                try
                {
                    for (int i = 0; i < 10; i++)
                    {
                        Console.WriteLine(i);
                        Thread.Sleep(100);
                    }
                }
                catch (ThreadAbortException e)
                {
                    Console.WriteLine("辅助线程被提前中断"+e.Message);
                    Thread.ResetAbort();
                }
                finally
                {
                    Console.WriteLine("完成清理辅助线程的工作");
                }
                Console.WriteLine("辅助线程结束");
            }
        }
    }
    Background属性为true的线程称为"背景线程",即主线程结束时让CLR自动地强行结束所有还在运行的辅助线程


    可以使用Thread.Sleep方法让线程对像休息一段特定的时间后再继续(称为线程休眠)
    Thread.Sleep(3000);
    Thread.Sleep方法的调用将导致线程从running状态转换为waitsleepjoin状态,这将导致一个线程上下文的切换,因而会对程序性能有一定影响。如果仅仅是希望线程能等待一定的时间,即让线程在一段时间内空转,可以调用Thread.SpinWait。

    等待一个线程的完成
    主线程启动一个辅助线程后,要等待它运行结束后才能继续干某些工作
    线程之间的这种协作关系称为”线程同步”
    有关JOIN的方法用图来分析,在线程A的执行流程中调用线程B的JOIN方法,其实是相当于把线程B的整个执行流程”嵌入”线程A的执行流程中。线程A会在调用JOIN方法的那句代码处等待线程B的工作完成。因此,在这种情况下必须保证线程B是可以在有限时间内结束的,可以想像整个程序会始处于等待状态。这种状态称为”死锁”。

    2.Windows操作系统线程调度策略

    在.net中每个托管线程都拥有一系列的状态,在任何时候每个线程对像一定处于一个确定的状态之中,可通过Thread类的ThreadState属性了解线程对像所处的状态
    线程状态。

    线程状态

    说明

    Abort

    线程处于Stopped状态中

    AbortRequested

    已对线程调用了Thread.Abort方法,但线程尚未接收到试图终止它挂起的Threading.ThreadAbortException

    Background

    线程正作为后台线程执行,相对于前台线程而言

    Running

    线程已启动,正在运行中

    Stopped

    线程已停止

    StopRequested

    正在请求线程停止,仅在.Net Framework内部使用

    Suspended

    线程已挂起

    SuspendRequested

    正在请求线程挂起

    Unstarted

    尚未对线程调用Thread.Start方法

    WaitSleepJoin

    由于调用Montor.Enter申请锁,Sleep或Join线程已被阻止

    线程优先级
    每个线程还关联着一个线程优先级,由Thread.Priority属性标识
    .NET Framwork提供了五种线程优先级,从高到低依次为
    Highest—>AboveNormal->Normal->BelowNormal->Lowest
    新创建的线程对像具体优先级”Normal”,但可以将它的Priority属性更改为上述的任何一个值。


    操作系统线程调度策略
    Windows操作系统按照时间片来将CPU分配给各个线程使用,由于可能有多个线程正处于Running状态,Windows就按照线程优先级将等待分配的CPU线程分成几个队列,根据特定的线程调度算法从队列中选一个线程运行。当高优先级队列中还有线程等待分配CPU时,低优先级的线程就只能等待了。如果高优先级的线程都已执行完毕,则线程调度程序为低优先级线程队列中的线程分配CPU运行。如果某一线程正在CPU执行,这时windows发现一个高优先级的线程进入就绪队列等待运行,则在当前这个低优先级的线程执行完一个时间片后,操作系统会进行一次线程调度,以让这个新到的高优先级线程有机会获取CPU运行。
    一个线程优先级不直接影响该线程的状态,只影响windows选中它占用 CPU运行的时间的概率。

    3.向线程函数传送信息的方式

    使用外壳方法
    线程类Thread接收一个ThreadStart委托类型的参数,而ThreadStart不能有返回值,也没有参数。这就大大限制了线程函数的选择,因此不得不手动增加一个符合ThreadStart委托要求的"外壳"方法,示例如:

        class Program
        {
            static void Main(string[] args)
            {
                MyThread2 obj = new MyThread2 { x = 10, y = 20 };
                Thread th = new Thread(obj.ThreadMethod);
                th.Start();
                th.Join();
                Console.WriteLine("结果为:" + obj.returnValue.ToString());
                Console.ReadKey();

            }
        }
        class MyThread2
        {
            public int x;
            public int y;
            public long returnValue;
            public void ThreadMethod()
            {
                returnValue = SomeFunc(x, y);
            }
            
            long SomeFunc(int x, int y)
            {
                long ret = 0;
                return ret = x + y;
            }

        }

    使用带参数的ParameterIzedThreadStart
    .NET2.0给Thread类加入一个重载形式
    public Thread(ParameterizedThreadStart start);
    ParameterizedThreadStart委托可以接收含有一个参数且无返回值的线程函数,由于其参数类型为Object,所以此委托可接收任何类型的方法参数。示例如:
    class MyThread
    {
        public void Method1(object x){......}
        public void Method2(object str){......}
    }
    Thread th1=new Thread(new ParameterizedThreadStart(Method1));
    Thread th2=new Thread(new ParameterizedThreadStart(Method2));
    th1.Start(100);
    th2.Start("Hello");
    使用ParameterIzedThreadStart委托,可以避免将线程函数的参数外化为类的字段,但还是有限制,即纯种函数只能有一个参数,且不能有返回值

    设计线程信息输入输出辅助类
    见下方示例代码,代码一看都明白了。

    namespace UseArray
    {
        class Program
        {
            static void Main(string[] args)
            {
                ThreadMethodHelper argu = new ThreadMethodHelper();
                argu.arr = new int[] { -1, 9, 100, 78, 20, 56 };
                Thread th = new Thread(DoWithArray);
                th.Start(argu);
                th.Join();
                Console.WriteLine("数组元素清单");
                for (int i = 0; i < argu.arr.Length; i++)
                {
                    Console.WriteLine(i + " ");
                }
                Console.ReadKey();
            }
            static void DoWithArray(object obj)
            {
                ThreadMethodHelper argu = obj as ThreadMethodHelper;
                for (int i = 0; i < argu.arr.Length; i++)
                {
                    if (argu.arr[i] > argu.MaxVal)
                        argu.MaxVal = argu.arr[i];
                    if (argu.arr[i] < argu.MinVal)
                        argu.MinVal = argu.arr[i];
                    argu.Sum += argu.arr[i];
                }
                argu.Average = argu.Sum / argu.arr.Length;
            }
        }
        class ThreadMethodHelper
        {
            public int[] arr;
            public int MaxVal=0;
            public int MinVal=0;
            public long Sum=0;
            public double Average = 0;

        }
    }

    4. 线程统一取消模型

    在多线程程序运行中,由用户取消操作是一种非常常见的场景,比如用户使用windows资源管理器在当前文件夹中搜索文件时,可以通过点击其它文件夹而取消搜索。
    中途停止一个线程的执行,通常用Thread.Abort方法,但这种方式会造成程序涉及的数据完整性受到破坏,线程所占用的一些系统资源(比如文件句柄等)也可能无法完成。比较合理的方式是外界提出"取消操作"的请求,然后由线程自身来决定如何处理这一请求。
    在设计多线程程序时,可设置一个用于接收外部取消消息的属性,然后在线程函数中分阶段地检测这一属性,每个阶段的检查点由软件开发者确定,并且决定线程如何优雅退出。

  • 相关阅读:
    九种常用排序的性能分析总结
    C语言输出格式总结
    线程安全的单例模式
    反射原理
    二进制的计算(计算机为什么采用补码存储数据)
    java程序员必须会的技能
    09网易校园招聘笔试题
    Spring获取ApplicationContext方式,和读取配置文件获取bean的几种方式
    【转】策略与机制分离
    VM PowerCli的简单安装和使用学习
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/3292014.html
Copyright © 2011-2022 走看看