線程初步了解 - <第一篇>


  操作系統通過線程對程序的執行進行管理,當操作系統運行一個程序的時候,首先,操作系統將為這個准備運行的程序分配一個進程,以管理這個程序所需要的各種資源。在這些資源之中,會包含一個稱為主線程的線程數據結構,用來管理這個程序的執行狀態。

  在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());
        }
    }

  輸出如下:

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM