多线程开发技术基础

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

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

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

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

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;

    }
}

原文地址:https://www.cnblogs.com/75115926/p/3291657.html