1. 程式人生 > >乾貨分享:詳解執行緒的開始和建立

乾貨分享:詳解執行緒的開始和建立

C#多執行緒之旅目錄:

C#多執行緒之旅(5)——同步機制介紹

C#多執行緒之旅(6)——詳解多執行緒中的鎖

更多文章正在更新中,敬請期待......

C#多執行緒之旅(2)——建立和開始執行緒

程式碼下載

第一篇~第三篇的程式碼示例:

一、執行緒的建立和開始

在第一篇的介紹中,執行緒使用Thread 類的建構函式來建立,通過傳給一個ThreadStart 委託來實現執行緒在哪裡開始執行。下面是ThreadStart的定義:

// Summary:
//     Represents the method that executes on a System.Threading.Thread.
[ComVisible(true)] public delegate void ThreadStart();

呼叫一個Start方法,然後設定它開始執行。執行緒會一直執行直到這個方法返回,然後這個執行緒結束。

下面是一個例子,使用擴充套件C#語法建立一個ThreadStart委託:2.1_ThreadStart

 1 class ThreadTest
 2 {
 3     static void Main()
 4     {
 5         Thread t = new Thread(new ThreadStart(Go));
 6         t.Start(); 
7 Go(); 8 Console.ReadKey(); 9 } 10 static void Go() 11 { 12 Console.WriteLine("hello!"); 13 } 14 }

在這個例子中,thread t執行Go(),基本上與主線同時程呼叫Go()方法,結果是打印出兩個時間接近的hello

一個執行緒可以被方便的建立通過指定一個方法組,然後由C#推斷出ThreadStart委託:2.2_Thread

 1 class Program
 2 {
 3     static
void Main(string[] args) 4 { 5 Thread t = new Thread(Go); 6 t.Start(); 7 Go(); 8 Console.ReadKey(); 9 } 10 11 static void Go() 12 { 13 Console.WriteLine("Go"); 14 } 15 }

另外一種更簡單的方式是使用lambda表示式或者匿名方法:2.3_LambaExpression

static void Main(string[] args)
{
    Thread t = new Thread(()=>Console.WriteLine("Go"));
    t.Start();
    Console.ReadKey();
}

二、傳遞資料給一個執行緒

1.利用Lambda傳遞一個數據

傳遞引數給執行緒的目標方法的最簡單的方法是執行一個lambda表示式,該表示式呼叫一個方法並傳遞期望的引數給這個方法。

2.4_PassingDataToAThread

static void Main(string[] args)
{
    Thread t = new Thread(() => Print("A"));
    t.Start();
    Console.ReadKey();
}

static void Print(string message)
{
    Console.WriteLine(message);
}

2.傳遞多個引數

通過這種方式,你可以傳遞任意數量的引數給這個方法。你甚至可以將整個實現包裝在一個多語句的lambda中:

2.5_PassingDataToAThread

new Thread(() =>
{
    Console.WriteLine("a");
    Console.WriteLine("b");
}).Start();

你也可以簡單的在C# 2.0裡面那樣使用匿名方法做同樣的事:

new Thread(delegate()
{
    Console.WriteLine("a");
    Console.WriteLine("b");
}).Start();

3.利用Thread.Start傳遞引數

另外一種方式是傳遞一個引數給ThreadStart方法:

2.6_PassingDataToAThread_ThreadStart

static void Main(string[] args)
{
    Thread t = new Thread(Print);
    t.Start("A");
    Console.ReadKey();
}
static void Print(object messageObj)
{
    string message = (string)messageObj;//必須進行轉換
    Console.WriteLine(message);
}

這種方式能夠工作是因為Thread的建構函式是過載的,接受下面兩種中的任意一種委託:

// Summary:
//     Represents the method that executes on a System.Threading.Thread.
[ComVisible(true)]
public delegate void ThreadStart();

// Summary:
//     Represents the method that executes on a System.Threading.Thread.
//
// Parameters:
//   obj:
//     An object that contains data for the thread procedure.
[ComVisible(false)]
public delegate void ParameterizedThreadStart(object obj);

這個ParameterizedThreadStart的只允許接收一個引數。而且因為它的型別是object,所以通常需要轉換。

4.Lambda表示式和捕獲變數

由我們上面看到的例子可以知道,一個lambda式在傳遞資料給執行緒是最用的。然而,你必須非常小心在開始執行緒後意外修改捕獲變數,因為這些變數是共享的。比如下面的:

2.7_LbdaExpressionsAndCapturedVariables

for(int i =0;i<10;i++)
{
    new Thread(() => Console.Write(i)).Start();
}

這個輸出是不確定的,下面是一種典型的情況:

這裡的問題是變數ifor迴圈執行時指向同一個記憶體地址。因此,每一個執行緒呼叫Console.Write時,i的值有可能在這個執行緒執行時改變。

解決方案是使用一個臨時變數:

2.8_LambdaExpressionsAndCapturedVariables_Solution

for (int i = 0; i < 10; i++)
{
    int temp = i;
    new Thread(() => Console.Write(temp)).Start();
}

變數temp在每個迴圈迭代中位於不同的記憶體塊。因此每一個執行緒捕獲到了不同的記憶體位置,而且沒有問題。我們可以解釋在之前的程式碼中的問題:

2.9_PassingData_TemporaryVariable

string text = "A";
Thread a = new Thread(() => Console.WriteLine(text));

text = "B";
Thread b = new Thread(() => Console.WriteLine(text));

a.Start();
b.Start();

因為兩個lambda表示式捕獲同樣的text的值,所以B被打印出兩次。

三、命名執行緒

每一個執行緒有一個Name屬性你可以方便用來debugging.當執行緒顯示在Visual Statudio裡面的Threads WindowDebug Loaction toolbar的時候,執行緒的Name屬性是特別有用的。你可以只設置執行緒的名字一次;之後嘗試改變它將會丟擲異常資訊。

靜態的Thread.CurrentThread屬性代表當前執行的執行緒。

在下面的例子2.10_NamingThread中,我們設定了主執行緒的名字:

static void Main(string[] args)
{
    Thread.CurrentThread.Name = "Main Thread";
    Thread t = new Thread(Go);
    t.Name = "Worker Thread";
    t.Start();
    Go();
    Console.ReadKey();
}
static void Go()
{
    Console.WriteLine("Go! The current thread is {0}", Thread.CurrentThread.Name);
}

四、前臺執行緒和後臺執行緒

預設情況下,你自己顯示建立的執行緒是前臺執行緒。前臺執行緒保持這個應用程式一直存活只要其中任意一個正在執行,而後臺執行緒不是這樣的。一旦所有的前臺執行緒完成,這個應用程式就結束了, 任何正在執行的後臺執行緒立刻終止。

一個執行緒前臺/後臺的狀態跟它的優先順序和配置的執行時間沒有關聯。

你可以使用執行緒的IsBackgroud屬性查詢或改變一個執行緒的後臺狀態。

下面是例子:2.11_PriorityTest

static void Main(string[] args)
{
    Thread t = new Thread(() => Console.ReadKey());
    if (args.Length > 0)//如果Main方法沒有傳入引數
    {
        //設定執行緒為後臺執行緒,等待使用者輸入。
        //因為主執行緒在t.Start()執行之後就會終止,
        //所以後臺執行緒t會在主執行緒退出之後,立即終止,應用程式就會結束。
        t.IsBackground = true;
    }
    t.Start();
}

如果程式呼叫的時候傳入了引數,則建立的執行緒為前臺執行緒,然後等待使用者輸入。

同時,如果主執行緒退出,應用程式將不會退出,因為前臺執行緒t沒有退出。

另一方面,如果main方法傳入了引數,則建立的執行緒設定為後臺執行緒。當主執行緒退出時,應用程式立即退出。

當一個程序以這種方式終止,則任何後臺執行緒執行棧裡面的finally 語句塊將會被規避。

如果你的執行緒使用finally(or using)語句塊去執行如釋放資源或者刪除臨時檔案的清理工作,這將是一個問題。為了避免這個,你可以顯示地等待後臺執行緒退出應用程式。

這裡有兩種實現方式:

  1. 如果你自己建立了這個執行緒,可以在這個執行緒上呼叫Join方法。
  2. 如果你使用執行緒池,可以使用一個事件去等待處理這個執行緒。

在這兩種情況下,你需要指定一個timeout,因此可以結束一個由於某些原因拒絕完成的執行緒。這是你的備選退出策略:在最後,你想要你的應用程式關閉,不需要使用者從工作管理員中刪除。

如果使用者使用工作管理員強制結束一個.NET程序,所有的執行緒像是後臺執行緒一樣終止。這個是觀察到的行為,所以會因為CLR和作業系統的版本而不同。

前臺執行緒不需要這樣對待,但是你必須小心避免可能造成執行緒不能結束的bugs。造成應用程式不能正確地退出的一個通常的原因是有啟用的前臺執行緒還存活在。

五、執行緒優先順序

一個執行緒的優先順序決定了在作業系統中它可以得到多少相對其他執行緒的執行時間,下面是執行緒優先順序的等級:

// Summary:
//     Specifies the scheduling priority of a System.Threading.Thread.
[Serializable]
[ComVisible(true)]
public enum ThreadPriority
{
    Lowest = 0,
    BelowNormal = 1,
    Normal = 2,
    AboveNormal = 3,
    Highest = 4,
}

當多執行緒同時是啟用的,執行緒優先順序是很重要的。

注意:提高執行緒優先順序時,需要非常小心,這將可能導致其他執行緒對資源訪問的飢餓狀態的問題。

當提升一個執行緒的優先順序時,不會使它執行實時工作,因為它被應用程式的程序優先順序限制了。為了執行實時工作,你也必須通過使用System.DiagnosticesProcess類來提升程序的優先順序:

using (Process p = Process.GetCurrentProcess())
{
    p.PriorityClass = ProcessPriorityClass.High;
}

ProcessPriorityClass.High事實上是優先順序最高的一檔:實時。設定一個程序優先順序到實時狀態將會導致其他執行緒無法獲得CPU時間片。如果你的應用程式意外地進入一個無限迴圈的狀態,你甚至會發現操作被鎖住了,只有電源鍵能夠拯救你了。針對這個原因,High通常對於實時應用程式是最好的選擇。

如果你的實時應用程式有一個使用者介面,提高程式的優先順序將會使重新整理介面佔用昂貴的CPU的時間,且會使整個系統變得執行緩慢(尤其是UI很複雜的時候)。降低主執行緒優先順序且提升程序的優先順序來確保實時執行緒不會被介面重繪所搶佔,但是不會解決其他程序對CPU訪問缺乏的問題,因為作業系統整體上會一直分配不成比例的資源給程序。一個理想的解決方案是讓實時執行緒和使用者介面用不同的優先順序執行在不同的程序中,通過遠端和記憶體對映檔案來通訊。即使提高了程序優先順序,在託管環境中處理硬實時系統需求還是對適用性有限制。此外,潛藏的問題會被自動垃圾回收引進,作業系統會遇到新的挑戰,即使是非託管程式碼,使用專用硬體或者特殊的實時平臺,那將被最好的解決。

六、異常處理

在任何try/catch/finally 語句塊作用域內建立的執行緒,當這個執行緒開始時,這個執行緒和語句塊是沒有關聯的。

思考下面的程式:

 參考例子:2.12_ExceptionHandling

static void Main(string[] args)
{
    try
    {
        new Thread(Go).Start();
    }
    catch(Exception ex)
    {
        Console.WriteLine("Exception");
    }
    Console.ReadKey();
}
static void Go()
{
    throw null;
}

try/catch 宣告在這個例子中是無效的,而且新建立的執行緒將會被一個未處理的NullReferenceException所阻斷。當你考慮每一個執行緒有一個單獨的執行路徑這種行為是說得通的。

改進方法是將exception handler移到Go()的方法中:

參考例子:2.13_ExceptionHandling_Remedy

class Program
{
    static void Main(string[] args)
    {
        new Thread(Go).Start();
        Console.ReadKey();
    }

    static void Go()
    {
        try
        {
            throw null;
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

你需要在應用程式中的所有執行緒入口方法中新增一個exception handler ,就像你在主執行緒中做的那樣。一個未處理的執行緒會造成整個應用程式關閉,而且會彈出一個不好看的視窗。

在寫這個exception handling 語句塊時,你可能極少忽略這個問題,典型情況是,你可能會記錄exception的詳細資訊,然後可能顯示一個視窗讓使用者去自動去提交這些資訊到你的web server上。然後你可能會關掉這個應用程式-因為這個error毀壞了程式的狀態。然後,這樣做的開銷是使用者可能會丟失他最近的工作,比如開啟的文件。

對於WPFWinForm應用程式來說,全域性的exception handling 事件(Application.DispatcherUnhandlerException Application.ThreadException)只會檢測到主UI執行緒上的丟擲的異常。你還是必須手動處理執行緒的異常。

AppDomain.CurrentDomain.UnhandledException可以檢測任何未處理的異常,但是無法阻止應用程式之後關閉。

然而,某些情形下你不需要線上程上處理異常,因為.NET Framework為你做了這個。下面是沒有提及的內容:

Asynchronous delegates

BackgroudWorker

The Task Parallel Library(conditions apply)

相關推薦

乾貨分享執行開始建立

C#多執行緒之旅目錄: C#多執行緒之旅(5)——同步機制介紹 C#多執行緒之旅(6)——詳解多執行緒中的鎖 更多文章正在更新中,敬請期待...... C#多執行緒之旅(2)——建立和開始執行緒 程式碼下載 第一篇~第三篇的程式碼示例: 一、執行緒的建立和開始

Java基礎由JVM記憶體模型執行安全

1.前言 最近在研究JVM記憶體模型和Java基礎知識。主要講的是執行緒共享變數與執行緒私有變數以及如何寫出執行緒安全的程式碼。這裡列出一條規則,“類中的成員變數,也叫例項變數,也叫全域性變數,它是非執行緒安全,是所有執行緒共享的變數,定義在方法中的私有變數是執行緒安全的,是每個執行緒私

#乾貨java多執行高階教程,這些你都懂了嗎?

一、countdownLatch和cyclicbarrier(這兩個做多執行緒控制很好用,工作中會經常用到) countdownLatch:主執行緒阻塞,當多個執行緒countdown到0,主執行緒執行; cyclicbarrier:多個執行緒等待,當都處於等待

雷林鵬分享Ruby 多執行

  Ruby 多執行緒   每個正在系統上執行的程式都是一個程序。每個程序包含一到多個執行緒。   執行緒是程式中一個單一的順序控制流程,在單個程式中同時執行多個執行緒完成不同的工作,稱為多執行緒。   Ruby 中我們可以通過 Thread 類來建立多執行緒,Ruby的執行緒是一個輕量級的,可以以高效

JAVA技術分享消失的執行

  很多小夥伴都問過我一個問題,就是任務執行緒跑著跑著消失了,而且沒有任何異常日誌。我都是條件反射式的回覆,是不是用了執行緒池的submit提交任務。而且很大機率對方給予肯定答覆。   解決方案,很多人都聽過不少,下面我就分析一下原因以及最佳實踐。   為什麼消失   submit這個單詞用的真的特

執行——執行基本概念

0. 簡介 這個系列開始來講解 Java 多執行緒的知識,這節就先講解多執行緒的基本知識。 1. 程序與執行緒 1.1 什麼是程序? 程序就是在執行過程中的程式,就好像手機執行中的微信,QQ,這些就叫做程序。 1.2 什麼是執行緒? 執行緒就是程序的執行單元,就好像一個音樂軟體可以聽音樂,下載音樂,這些任務

執行池的作用及Java中如何使用執行

![](https://img2020.cnblogs.com/other/1815316/202101/1815316-20210114083352253-122112091.png) 服務端應用程式(如資料庫和 Web 伺服器)需要處理來自客戶端的高併發、耗時較短的請求任務,所以頻繁的建立處理這些請求的所

Java併發程式設計4種執行緩衝佇列BlockingQueue

一. 執行緒池簡介 1. 執行緒池的概念:           執行緒池就是首先建立一些執行緒,它們的集合稱為執行緒池。使用執行緒池可以很好地提高效能,執行緒池在系統啟動時即建立大量空閒的執行緒,程式將一個任務傳給執行緒池,執行緒池就會啟動一

Linux使用多執行程式設計訊息佇列,實現兩個程序之間的聊天

思路: 一個檔案:建立一個執行緒和主函式,或者建立兩個執行緒主函式呼叫(我用這種)。 建立兩個訊息佇列, 一共兩個檔案,兩個佇列,四個程序 a.c    一個程序寫(訊息型別為1)   ---->>佇列     一個程序讀(訊息型別為2) b.c   一

【linux】Valgrind工具集(十三)DRD(執行錯誤檢測器)

一、概述 多執行緒程式設計需要注意的問題: 資料競爭;鎖競爭;POSIX執行緒API使用不當;死鎖; 二、使用 1、例子main.c原始碼 #include <stdio.h> #include <pthread.h> #include <s

【linux】Valgrind工具集(十三)Helgrind(執行錯誤檢測器)

一、概述 Helgrind用於檢測C、C ++和Fortran程式中使用符合POSIX標準的執行緒函式造成的同步錯誤。 POSIX中關於執行緒的主要抽象描述有:共享公共地址空間的一組執行緒、執行緒建立、執行緒連線、執行緒退出、互斥(鎖)、條件變數(執行緒間事件通知)、讀寫器鎖、自

執行--thisThread.currentThread()

在看多執行緒程式設計核心技術的時候,有一段程式碼讓我很困惑,所以在這裡記錄一下。 public class isalive { public static void main(String[] args) { // TODO Auto-generated method stub //T

#乾貨分享Java 的泛型擦除執行時泛型資訊獲取

Java 的泛型擦除 程式設計師界有句流行的話,叫 talk is cheap, show me the code,所以話不多說,看程式碼。 如果有想學習java的程式設計師,可來我們的java學習扣qun:79979,2590免費送java的視訊教程噢!我整理了一份適合18年學習的java

Tomcat連線數執行

轉載至http://www.importnew.com/27309.html 前言 在使用tomcat時,經常會遇到連線數、執行緒數之類的配置問題,要真正理解這些概念,必須先了解Tomcat的聯結器(Connector)。 在前面的文章 詳解Tomcat配置檔案server.xml 中寫

tomcat高併發下優化及連線數執行

高併發環境下,我知道優化配置tomcat,對連線數和執行緒池作修改,最重要的是connector的協議Http Connector使用NIO,而不是預設的AJP Connector,當時也沒有仔細研究其原理。現在來為以上這些設定做一下剖析。 要了解這些,不能避開tomcat

postman(六)在Pre-request Script中如何執行請求

上一篇藉著如何在不同介面之間傳遞資料簡單說了下如何在postman編寫指令碼來發送請求,這裡再詳細介紹一下如何在Pre-request Script和Tests標籤中編寫指令碼來。因為我目前研究的也不是很深,對js也只是瞭解一點皮毛,所以大部分還是依賴postman已經封裝好的方法來介紹 這裡以P

Java多執行同步非同步

1. 多執行緒併發時,多個執行緒同時請求同一資源,必然導致此資源的資料不安全。 2. 執行緒池 在WEB服務中,對於web伺服器的響應速度必須儘可能的快,這就容不得在使用者提交請求按鈕後,再建立執行緒提供服務。為了減少使用者的等待時間,執行緒必須預先建立,放線上程池中,執行

innoDB 儲存引擎 IO THread 後臺執行 innodb_read_io_threads innodb_write_io_threads 兩個引數記錄

在innodb儲存中大量使用了AIO(Async IO)來處理些IO請求,這樣可以極大提高資料庫效能。而IO Thread 的主要工作是要負責這些IO請求的回撥(call back)處理。innodb1.0版本之前共有4個IO Thread,分別是write,read,ins

從零開始Desire HD刷機指南 —— 第七章SHIP ENG S-ON S-OFF

原文地址:http://blog.sina.com.cn/s/blog_722b43a60100q5i9.html 本教程由symen 原創,轉載請註明出處。 在教程的第四章裡,曾經教大家如何檢視手機資訊,當中簡單介紹了幾個名詞,由於這幾個名詞對刷機來說比較重要,所

執行MT執行MD的區別

區別1:全域性堆控制代碼不一樣。 網上有一個說法,就是一個執行緒一個棧,一個模組一個堆。前者很容易有理解,每個執行緒建立的時候在CreateThread中都能制定預設棧大小,只是很多情況下都取了預設值。而一個模組一個堆呢?其實很簡單測試,如果是一個多執行緒MT編譯方式的程