說說C# 多線程那些事-線程基礎

来源:http://www.cnblogs.com/yinrq/archive/2016/04/21/5412238.html
-Advertisement-
Play Games

我第一次接觸“線程”的概念時,覺得它深奧難懂,看了好多本書,花了很長時間才領悟到它的真諦。現在我就以一個初學者的心態,把我所理解的“多線程”描述給大家。這一次是系列文章,比較完整的展示與線程相關的基本概念。希望對初學者有所幫助。 如果你是高手,請你別繼續看,會浪費你寶貴的時間。 一、基本概念 什麼是 ...


我第一次接觸“線程”的概念時,覺得它深奧難懂,看了好多本書,花了很長時間才領悟到它的真諦。現在我就以一個初學者的心態,把我所理解的“多線程”描述給大家。這一次是系列文章,比較完整的展示與線程相關的基本概念。希望對初學者有所幫助。

如果你是高手,請你別繼續看,會浪費你寶貴的時間。

一、基本概念

什麼是進程?

當一個程式開始運行時,它就是一個進程,進程包括運行中的程式和程式所使用到的記憶體和系統資源。 而一個進程又是由多個線程所組成的。

什麼是線程?

線程是程式中的一個執行流,每個線程都有自己的專有寄存器(棧指針、程式計數器等),但代碼區是共用的,即不同的線程可以執行同樣的函數。

什麼是多線程?

多線程是指程式中包含多個執行流,即在一個程式中可以同時運行多個不同的線程來執行不同的任務,也就是說允許單個程式創建多個並行執行的線程來完成各自的任務。

前臺線程後臺線程?

應用程式的主線程和通過構造一個Thread對象來顯式創建的任何線程都預設是前臺線程。相反線程池線程預設為後臺線程。另外由進入托管執行環境的本機代碼創建的任何線程都被標記為後臺線程。

線上程的生命周期中,任何時候都可以從前臺變為後臺,或者從後臺變為前臺。

前臺線程能阻止應用程式的終結。一直到所有的前臺線程終止後,CLR才能關閉應用程式(即卸載承載的應用程式域)。

後臺線程(有時也叫守護線程)被CLR認為是程式執行中可做出犧牲的途徑,即在任何時候(即使這個線程此時正在執行某項工作)都可能被忽略。因此,如果所有的前臺線程終止,當應用程式域卸載時,所以的後臺線程也會被自動終止。

 

線程是輕量級進程。一個使用線程的常見實例是現代操作系統中並行編程的實現。使用線程節省了 CPU 周期的浪費,同時提高了應用程式的效率。

二、線程的生命周期

線程生命周期開始於 System.Threading.Thread 類的對象被創建時,結束於線程被終止或完成執行時。

線程生命周期中的各種狀態:

未啟動狀態:當線程實例被創建但 Start 方法未被調用時的狀況(將該線程標記為可以運行的狀態,但具體執行時間由cpu決定。)。

就緒狀態:當線程準備好運行並等待 CPU 周期時的狀況。

不可運行狀態:下麵的幾種情況下線程是不可運行的:(已經調用 Sleep 方法,已經調用 Wait 方法,通過 I/O 操作阻塞)

死亡狀態:當線程已完成執行或已中止時的狀況。

三、線程

1、主線程

進程中第一個被執行的線程稱為主線程

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        static void Main(string[] args)
        {
            Thread th = Thread.CurrentThread;
            th.Name = "MainThread";
            Console.WriteLine("This is {0}", th.Name);
            Console.ReadKey();
        }
    }
}

輸出:This is MainThread

2、線程的創建

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            Console.WriteLine("Thread1 starts");
        }
        public static void Thread2(object data)
        {
            Console.WriteLine("Thread2 starts,para:{0}", data.ToString());
        }
        static void Main(string[] args)
        {
            var t1 = new Thread(Thread1);
            t1.Start();

            var t2 = new Thread(Thread2);
            t2.Start("thread2");
            Console.ReadKey();
        }
    }
}

輸入:

Thread1 starts

Thread2 starts,para:thread2

3、線程的管理

sleep()掛起和Abort() 銷毀線程

通過拋出 threadabortexception 在運行時中止線程。這個異常不能被捕獲,如果有 finally 塊,控制會被送至 finally 塊

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            Console.WriteLine("Thread1 starts");
            Console.WriteLine("Thread1 Paused for 5 seconds");
            Thread.Sleep(5000);
            Console.WriteLine("Thread1 resumes");
        }
        static void Main(string[] args)
        {
            var t1 = new Thread(Thread1);
            t1.Start();

            Console.ReadKey();
        }
    }
}
線程掛起代碼
using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1()
        {
            try
            {
                Console.WriteLine("Thread1 starts");
                for (int i = 0; i <= 10; i++)
                {
                    Thread.Sleep(500);
                    Console.WriteLine(i);
                }
                Console.WriteLine("Thread1 Completed");
            }
            catch (ThreadAbortException ex)
            {
                Console.WriteLine("Thread1 Abort Exception");
            }
            finally
            {
                Console.WriteLine("Couldn't catch the Thread1 Exception");
            }

        }
        static void Main(string[] args)
        {
            //開啟子線程
            var t1 = new Thread(Thread1);
            t1.Start();

            //主線程掛起2s
            Thread.Sleep(2000);

            //終止t1子線程
            t1.Abort();

            Console.ReadKey();
        }
    }
}
線程銷毀代碼

銷毀代碼執行結果:

四、線程池

在多線程程式中,線程把大部分的時間花費在等待狀態,等待某個事件發生,然後才能給予響應我們一般用ThreadPool(線程池)來解決;線程平時都處於休眠狀態,只是周期性地被喚醒我們使用使用Timer(定時器)來解決。

由於線程的創建和銷毀需要耗費一定的開銷,過多的使用線程會造成記憶體資源的浪費,出於對性能的考慮,於是引入了線程池的概念。線程池維護一個請求隊列,線程池的代碼從隊列提取任務,然後委派給線程池的一個線程執行,線程執行完不會被立即銷毀,這樣既可以在後臺執行任務,又可以減少線程創建和銷毀所帶來的開銷。線程池線程預設為後臺線程。

線程池自動管理線程線程的創建和銷毀。

代碼展示:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        public static void Thread1(object data)
        {
            Console.WriteLine("Thread1 => {0}",data.ToString());

        }
        static void Main(string[] args)
        {
            //控制線程數大小
            //第一個參數是:線程池中輔助線程的最大數目
            //第二個參數是:線程池中非同步 I/O 線程的最大數目
            ThreadPool.SetMaxThreads(3, 3);
            for (int i = 0; i < 10; i++)
            {
                //ThreadPool是靜態類無需實例化,
                //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
                ThreadPool.QueueUserWorkItem(Thread1, i);
            }
            Console.WriteLine("Thread1 sleep");
            Thread.Sleep(100000);

            Console.WriteLine("Thread1 end");
            Console.ReadKey();
        }
    }
}

運行結果:

但是為什麼最開始輸出Thread1 sleep?有時候也會在中間隨機輸出呢?

其實,線程池的啟動和終止不是我們程式所能控制的,線程池中的線程執行完之後是沒有返回值的,我們可以用ManualResetEvent通知一個或多個正在等待的線程已發生事件

修改後的代碼:

using System;
using System.Threading;

namespace Threading
{
    class Program
    {
        //新建ManualResetEvent對象並且初始化為無信號狀態
        private static ManualResetEvent mre = new ManualResetEvent(false);
        public static void Thread1(object data)
        {
            Console.WriteLine("Thread1 => {0}",data.ToString());
            if (Convert.ToInt32(data) == 9)
            {
                mre.Set();
            }
        }
        static void Main(string[] args)
        {
            //控制線程數大小
            //第一個參數是:線程池中輔助線程的最大數目
            //第二個參數是:線程池中非同步 I/O 線程的最大數目
            ThreadPool.SetMaxThreads(3, 3);
            for (int i = 0; i < 10; i++)
            {
                //ThreadPool是靜態類無需實例化,
                //ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1), i);
                ThreadPool.QueueUserWorkItem(Thread1, i);
            }

            //阻止當前線程,直到當前 WaitHandle 收到信號為止。 
            mre.WaitOne(Timeout.Infinite, true);

            Console.WriteLine("Thread1 sleep");
            Thread.Sleep(100000);

            Console.WriteLine("Thread1 end");
            Console.ReadKey();
        }
    }
}

輸入結果:

 ok,搞定。

 參考資料:

ThreadPool:https://msdn.microsoft.com/zh-cn/library/system.threading.threadpool.aspx#Y0

ManualResetEvent:https://msdn.microsoft.com/zh-cn/library/system.threading.manualresetevent.aspx

五、總結

多線程的好處:

可以提高CPU的利用率。在多線程程式中,一個線程必須等待的時候,CPU可以運行其它的線程而不是等待,這樣就大大提高了程式的效率。

 

多線程的不利方面:

線程也是程式,所以線程需要占用記憶體,線程越多占用記憶體也越多;

多線程需要協調和管理,所以需要CPU時間跟蹤線程;

線程之間對共用資源的訪問會相互影響,必須解決競用共用資源的問題;

線程太多會導致控制太複雜,最終可能造成很多Bug;

 

六、加關註

如果本文對你有幫助,請點擊右下角【好文要頂】和【關註我

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • • 關於PXE無盤工作站系統的簡介 PXE無盤工作站系統是指由一臺或多台“系統伺服器”和多台“PXE客戶端(無盤工作站)”通過 交換機 相連組成的區域網系統。 (圖1:無盤工作站系統部署拓撲圖) • 系統伺服器:通過DHCP+TFTP+NFS服務向無盤工作站提供系統支持 • PXE客戶端:PXE客戶 ...
  • 簡介: Pptpd 快速搭建 VPN 伺服器。 閑話不多說,動手 下載地址: http://hivelocity.dl.sourceforge.net/project/poptop/pptpd/pptpd-1.4.0/pptpd-1.4.0.tar.gz 1、安裝 2、配置 ## 其餘沒有標記的全為 ...
  • Qt Creator 你必須要掌握的快捷操作 多使用快捷鍵能顯著提高工作效率,儘可能減少鍵盤,滑鼠之間切換所浪費的時間。我這裡列出個人認為非常重要必須掌握的 Qt Creator 快捷鍵。看你知道幾個? Qt Creator 你必須要掌握的快捷操作 多使用快捷鍵能顯著提高工作效率,儘可能減少鍵盤,鼠 ...
  • 一Linux伺服器突然發送不出郵件,檢查了很多地方都沒有發現異常,檢查/var/log/maillog發現如下具體信息: Apr 12 00:36:04 mylinux sendmail[4685]: u3BGa4Is004685: Authentication-Warning: mylinux.x... ...
  • DNS伺服器介紹 DNS是電腦功能變數名稱系統(Domain Name System 或Domain Name Service) 的縮寫,它是由功能變數名稱解析器和功能變數名稱伺服器組成的。功能變數名稱伺服器是指保存有該網路中所有主機的功能變數名稱和對應IP地址,並具有將功能變數名稱轉換為IP地址功能的伺服器。其中功能變數名稱必須對應一個IP地址,一個... ...
  • 今天學到了很有意思的三個命令:怎麼有意思 快看下圖啦! 這是Ubuntu系統下的三個命令,跟echo本質一樣,但是輸出的字元是圖形的 ,很可愛! 以下是安裝命令: sudo apt-get update;sudo apt-get install sysvbanner sudo apt-get upd ...
  • 磁碟分析 本機的系統盤是C盤,操作系統是Windows 7 專業版,通過磁碟屬性可以看到C盤的已用空間是69.4G。 而我們運行自己編寫的腳本(腳本程式參考附錄,統計原理:計算目錄下各個文件的大小,然後相加,即為該目錄的大小;再將各個目錄占用空間相加,即為總的占用空間),可以發現,占用空間為59G。 ...
  • 由於需要安裝hadoop集群,有10台機器需要安裝,一開始打算用SCP複製,後來覺得不可接受(實際現場可能數倍的機器集群,就是10台也不想乾)。後來在網上找了,發現了clustershell和pssh這兩個工具。這兩個工具隨便用其中一個就可以了。 環境說明:centos6.5機器10台 需求:確定一... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...