執行緒

執行緒

執行緒,計算機科學術語,有時也被稱為輕量級進程(Light Weight Process,LWP),它是運行中的程式的調度單位。一個標準的執行緒由執行緒ID,當前指令指針(PC),暫存器集合和堆疊組成。執行緒被包含在進程之中,是進程的一個實體,是CPU調度和分派的基本單位它是比進程更小的能獨立運行的基本單位。執行緒自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組暫存器和棧),但是它可與同屬一個進程的其他的執行緒共享進程所擁有的全部資源。

基本信息

執行緒概念

執行緒執行緒
一般來說,我們把正在計算機中執行的程式叫做"進程"(Process),而不將其稱為程式(Program)。所謂"執行緒"(Thread),是"進程"中某個單一順序的控制流。
新興的作業系統,如Mac,WindowsNT,Windows95等,大多採用多執行緒的概念,把執行緒視為基本執行單位。執行緒也是Java中的相當重要的組成部分之一。
甚至最簡單的Applet也是由多個執行緒來完成的。在Java中,任何一個Applet的paint()和update()方法都是由AWT(AbstractWindowToolkit)繪圖與事件處理執行緒調用的,而Applet主要的里程碑方法——init(),start(),stop()和destory()——是由執行該Applet的套用調用的。
單執行緒的概念沒有什麼新的地方,真正有趣的是在一個程式中同時使用多個執行緒來完成不同的任務。某些地方用輕量進程(LightweightProcess)來代替執行緒,執行緒與真正進程的相似性在於它們都是單一順序控制流。然而執行緒被認為輕量是由於它運行於整個程式的上下文內,能使用整個程式共有的資源和程式環境。
作為單一順序控制流,在運行的程式內執行緒必須擁有一些資源作為必要的開銷。例如,必須有執行堆疊和程式計數器。線上程內執行的代碼只在它的上下文中起作用,因此某些地方用"執行上下文"來代替"執行緒"。

發展歷史

執行緒的引入
60年代,在OS中能擁有資源和獨立運行的基本單位是進程,然而隨著計算機技術的發展,進程出現了很多弊端,一是由於進程是資源擁有者,創建、撤消與切換存在較大的時空開銷,因此需要引入輕型進程;二是由於對稱多處理機(SMP)出現,可以滿足多個運行單位,而多個進程並行開銷過大。
因此在80年代,出現了能獨立運行的基本單位——執行緒(Threads)。

工作原理

執行緒與程式執行緒

執行緒是進程中的實體,一個進程可以擁有多個執行緒,一個執行緒必須有一個父進程。執行緒不擁有系統資源,只有運行必須的一些數據結構;它與父進程的其它執行緒共享該進程所擁有的全部資源。進程可以創建和撤消執行緒,從而實現程式的並發執行。一般,執行緒具有就緒、阻塞和運行三種基本狀態。

在多中央處理器的系統里,不同執行緒可以同時在不同的中央處理器上運行,甚至當它們屬於同一個進程時也是如此。大多數支持多處理器的作業系統都提供編程接口來讓進程可以控制自己的執行緒與各處理器之間的關聯度(affinity)。

有時候,執行緒也稱作輕量級進程。就象進程一樣,執行緒在程式中是獨立的、並發的執行路徑,每個執行緒有它自己的堆疊、自己的程式計數器和自己的局部變數。但是,與分隔的進程相比,進程中的執行緒之間的隔離程度要小。它們共享記憶體、檔案句柄和其它每個進程應有的狀態。

進程可以支持多個執行緒,它們看似同時執行,但互相之間並不同步。一個進程中的多個執行緒共享相同的記憶體地址空間,這就意味著它們可以訪問相同的變數和對象,而且它們從同一堆中分配對象。儘管這讓執行緒之間共享信息變得更容易,但您必須小心,確保它們不會妨礙同一進程里的其它執行緒。

Java 執行緒工具和 API 看似簡單。但是,編寫有效使用執行緒的複雜程式並不十分容易。因為有多個執行緒共存在相同的記憶體空間中並共享相同的變數,所以您必須小心,確保您的執行緒不會互相干擾。

相關概念

為了正確有效地使用執行緒,必須理解執行緒的各個方面並了解Java 實時系統。必須知道如何提供執行緒體、執行緒的生命周期、實時系統,如何調度執行緒、執行緒組、什麼是幽靈執行緒(Demo nThread)。

(1)執行緒體

所有的操作都發生線上程體中,在Java中執行緒體是從Thread類繼承的run()方法,或實現Runnable接口的類中的run()方法。當執行緒產生並初始化後,實時系統調用它的run()方法。run()方法內的代碼實現所產生執行緒的行為,它是執行緒的主要部分。

(2)執行緒狀態

附圖表示了執行緒在它的生命周期內的任何時刻所能處的狀態以及引起狀態改變的方法。這圖並不是完整的有限狀態圖,但基本概括了執行緒中比較感興趣和普遍的方面。以下討論有關執行緒生命周期以此為據。

新執行緒態(New Thread)
產生一個Thread對象就生成一個新執行緒。當執行緒處於"新執行緒"狀態時,僅僅是一個空執行緒對象,它還沒有分配到系統資源。因此只能啟動或終止它。任何其他操作都會引發異常。
可運行態(Runnable)
start()方法產生運行執行緒所必須的資源,調度執行緒執行,並且調用執行緒的run()方法。在這時執行緒處於可運行態。該狀態不稱為運行態是因為這時的執行緒並不總是一直占用處理機。特別是對於只有一個處理機的PC而言,任何時刻只能有一個處於可運行態的執行緒占用處理 機。Java通過調度來實現多執行緒對處理機的共享。

非運行態(Not Runnable)
當以下事件發生時,執行緒進入非運行態。
①suspend()方法被調用;
②sleep()方法被調用;
③執行緒使用wait()來等待條件變數;
④執行緒處於I/O等待。

死亡態(Dead)
當run()方法返回,或別的執行緒調用stop()方法,執行緒進入死亡態 。通常Applet使用它的stop()方法來終止它產生的所有執行緒。

(3)執行緒優先權

雖然我們說執行緒是並發運行的。然而事實常常並非如此。正如前面談到的,當系統中只有一個CPU時,以某種順序在單CPU情況下執行多執行緒被稱為調度(scheduling)。Java採用的是一種簡單、固定的調度法,即固定優先權調度。這種算法是根據處於可運行態執行緒的相對優先權來實行調度。當執行緒產生時,它繼承原執行緒的優先權。在需要時可對優先權進行修改。在任何時刻,如果有多條執行緒等待運行,系統選擇優先權最高的可運行執行緒運行。只有當它停止、自動放棄、或由於某種原因成為非運行態低優先權的執行緒才能運行。如果兩個執行緒具有相同的優先權,它們將被交替地運行。

Java實時系統的執行緒調度算法還是強制性的,在任何時刻,如果一個比其他執行緒優先權都高的執行緒的狀態變為可運行態,實時系統將選擇該執行緒來運行。

(4)幽靈執行緒

任何一個Java執行緒都能成為幽靈執行緒。它是作為運行於同一個進程內的對象和執行緒的服務提供者。例如,HotJava瀏覽器有一個稱為" 後台圖片閱讀器"的幽靈執行緒,它為需要圖片的對象和執行緒從檔案系統或網路讀入圖片。

幽靈執行緒是套用中典型的獨立執行緒。它為同一套用中的其他對象和執行緒提供服務。幽靈執行緒的run()方法一般都是無限循環,等待服務請求。

(5)執行緒組

每個Java執行緒都是某個執行緒組的成員。執行緒組提供一種機制,使得多個執行緒集於一個對象內,能對它們實行整體操作。譬如,你能用一個方法調用來啟動或掛起組內的所有執行緒。Java執行緒組由ThreadGroup類實現。
當執行緒產生時,可以指定執行緒組或由實時系統將其放入某個預設的執行緒組內。執行緒只能屬於一個執行緒組,並且當執行緒產生後不能改變它所屬的執行緒組。

執行緒好處

使用執行緒可以把占據長時間的程式中的任務放到後台去處理,用戶界面可以更加吸引人,這樣比如用戶點擊了一個按鈕去觸發某些事件的處理,可以彈出一個進度條來顯示處理的進度·程式的運行速度可能加快·在一些等待的任務實現上如用戶輸入、檔案讀寫和網路收發數據等,執行緒就比較游泳了。在這種情況下我們可以釋放一些珍貴的資源如記憶體占用等等。

與進程的比較

進程是資源分配的基本單位。所有與該進程有關的資源,都被記錄在進程控制塊PCB中。以表示該進程擁有這些資源或正在使用它們。
另外,進程也是搶占處理機的調度單位,它擁有一個完整的虛擬地址空間。

與進程相對應,執行緒與資源分配無關,它屬於某一個進程,並與進程內的其他執行緒一起共享進程的資源。
當進程發生調度時,不同的進程擁有不同的虛擬地址空間,而同一進程內的不同執行緒共享同一地址空間。

執行緒只由相關堆疊(系統棧或用戶棧)暫存器和執行緒控制表TCB組成。暫存器可被用來存儲執行緒內的局部變數,但不能存儲其他執行緒的相關變數。

發生進程切換與發生執行緒切換時相比較,進程切換時涉及到有關資源指針的保存以及地址空間的變化等問題;執行緒切換時,由於同不進程內的執行緒共享資源和地址 空間,將不涉及資源信息的保存和地址變化問題,從而減少了作業系統的開銷時間。而且,進程的調度與切換都是由作業系統核心完成,而執行緒則既可由作業系統內 核完成,也可由用戶程式進行。

適用範圍

執行緒執行緒

典型的套用

1.伺服器中的檔案管理或通信控制

2.前後台處理

3.異步處理

基本操作

派生:執行緒在進程內派生出來,它即可由進程派生,也可由執行緒派生。

執行緒的狀態與操作執行緒的狀態與操作

阻塞(Block):如果一個執行緒在執行過程中需要等待某個事件發生,則被阻塞。
激活(unblock:如果阻塞執行緒的事件發生,則該執行緒被激活並進入就緒佇列。
調度(schedule):選擇一個就緒執行緒進入執行狀態。
結束(Finish):如果一個執行緒執行結束,它的暫存器上下文以及堆疊內容等將被釋放。

執行緒的另一個執行特性是同步。執行緒中所使用的同步控制機制與進程中所使用的同步控制機制相同。

狀態變化

(1)創建執行緒
當創建一個新的進程時,也創建一個新的執行緒,進程中的執行緒可以在同一進程中創建新的執行緒中創建新的執行緒。

(2)終止執行緒
可以正常終止自己,也可能某個執行緒執行錯誤,由其它執行緒強行終止。終止執行緒操作主要負責釋放執行緒占有的暫存器和棧。

(3)阻塞執行緒

當執行緒等待每個事件無法運行時,停止其運行。

(4)喚醒執行緒
當阻塞執行緒的事件發生時,將被阻塞的執行緒狀態置為就緒態,將其掛到就緒佇列。進程仍然具有與執行相關的狀態。例如,所謂進程處於“執行”狀態,實際上是指該進程中的某執行緒正在執行。對進程施加的與進程狀態有關的操作,也對其執行緒起作用。例如,把某個進程掛起時,該進程中的所有執行緒也都被掛起,激活也是同樣。

執行緒分類

執行緒有兩個基本類型

用戶級執行緒

管理過程全部由用戶程式完成,作業系統核心心只對進程進行管理。

系統級執行緒

系統級執行緒也是核心級執行緒,由作業系統核心進行管理。作業系統核心給應用程式提供相應的系統調用和應用程式接口API,以使用戶程式可以創建、執行、撤消執行緒。

執行緒舉例

1.UnixInternational執行緒

SUN Solaris作業系統使用的執行緒叫做UnixInternational執行緒,支持核心執行緒、輕權進程和用戶執行緒。一個進程可有大量用戶執行緒;大量用戶執行緒復用少量的輕權進程,輕權進程與核心執行緒一一對應。
用戶級執行緒在調用核心服務時(如檔案讀寫),需要“捆綁(bound)”在一個lwp上。永久捆綁(一個LWP固定被一個用戶級執行緒占用,該LWP移到LWP池之外)和臨時捆綁(從LWP池中臨時分配一個未被占用的LWP)。
在調用系統服務時,如果所有LWP已被其他用戶級執行緒所占用(捆綁),則該執行緒阻塞直到有可用的LWP。
如果LWP執行系統執行緒時阻塞(如read()調用),則當前捆綁在LWP上的用戶級執行緒也阻塞。

執行緒用戶執行緒、輕權進程和核心執行緒的關係

UnixInternational執行緒的有關API

UNIX International 執行緒的頭檔案是<thread.h>。

1.創建用戶級執行緒

intthr_create(void*stack_base,size_tstack_size,void*(*start_routine)(void*),void*arg,longflags,thread_t*new_thr);

其中flags包括:THR_BOUND(永久捆綁),THR_NEW_LWP(創建新LWP放入LWP池),若兩者同時指定則創建兩個新LWP,一個永久捆綁而另一個放入LWP池。

2.等待用戶級執行緒

intthr_join(thread_twait_for,thread_t*dead,void**status);

3.掛起用戶級執行緒

intthr_suspend(thread_tthr);

4.繼續用戶級執行緒

intthr_continue(thread_tthr);

5.退出用戶級執行緒

voidthr_exit(void*status);

6.返回當前用戶級執行緒的執行緒標識符

thread_tthr_self(void);


2.POSIX執行緒

Pthreads(POSIX Thread)的相關API

Pthreads 執行緒的頭檔案是<pthread.h>。

1.創建用戶級執行緒

intpthread_create(pthread_t*thread,constpthread_attr_t*attr,void*(*start_routine)(void*),void*arg);

2.等待用戶級執行緒

intpthread_join(pthread_tthread,void**retval);

3.退出用戶級執行緒

voidpthread_exit(void*retval);

4.返回當前用戶級執行緒的執行緒標識符

pthread_tpthread_self(void);

5.用戶級執行緒的取消

intpthread_cancel(pthread_tthread);

3.C++11執行緒

C++11執行緒的頭檔案是<thread>。

1.創建用戶級執行緒

std::thread::thread(Function&&f,Args&&...args);

2.等待用戶級執行緒結束

std::thread::join();

3.脫離用戶級執行緒控制

std::thread::detach();

4.交換用戶級執行緒

std::thread::swap(thread&other);

4. C11執行緒

C11執行緒的頭檔案是<threads.h>。

C11執行緒僅僅是個“建議標準”,也就是說100%遵守C11標準的C編譯器是可以不支持C11執行緒的。根據C11標準的規定,只要編譯器預定義了__STDC_NO_THREADS__宏,就可以沒有<threads.h>頭檔案,自然也就也沒有下列函式。

1.創建用戶級執行緒

intthrd_create(thrd_t*thr,thrd_start_tfunc,void*arg);

2.結束本執行緒

_Noreturnvoidthrd_exit(intres);

3.等待用戶級執行緒運行完畢

intthrd_join(thrd_tthr,int*res);

4.返回當前執行緒的執行緒標識符

thrd_tthrd_current();

5. Win32執行緒

Win32執行緒的上下文包括:暫存器、核心棧、執行緒環境塊和用戶棧。

Win32執行緒狀態

(1) 就緒狀態:進程已獲得除處理機外的所需資源,等待執行。
執行緒
WindowsNT的執行緒狀態

(2) 備用狀態:特定處理器的執行對象,系統中每個處理器上只能有一個處於備用狀態的執行緒。
(3) 運行狀態:完成描述表切換,執行緒進入運行狀態,直到核心搶先、時間片用完、執行緒終止或進行等待狀態。
(4) 等待狀態:執行緒等待對象句柄,以同步它的執行。等待結束時,根據優先權進入運行、就緒狀態。
(5) 轉換狀態:執行緒在準備執行而其核心堆疊處於外存時,執行緒進入轉換狀態;當其核心堆疊調回記憶體,執行緒進入就緒狀態。
(6) 終止狀態:執行緒執行完就進入終止狀態;如執行體有一指向執行緒對象的指針,可將執行緒對象重新初始化,並再次使用。

Win32執行緒的有關API
Win32執行緒的頭檔案是<Windows.h>,僅適用於Windows作業系統。

1.創建用戶級執行緒

HANDLEWINAPICreateThread(LPSECURITY_ATTRIBUTESlpThreadAttributes,SIZE_TdwStackSize,LPTHREAD_START_ROUTINElpStartAddress,LPVOIDlpParameter,DWORDdwCreationFlags,LPDWORDlpThreadId);

2.結束本執行緒

VOIDWINAPIExitThread(DWORDdwExitCode);

3.掛起指定的執行緒

DWORDWINAPISuspendThread(HANDLEhThread);

4.恢復指定執行緒運行

DWORDWINAPIResumeThread(HANDLEhThread);

5.等待執行緒運行完畢

DWORDWINAPIWaitForSingleObject(HANDLEhHandle,DWORDdwMilliseconds);

6.返回當前執行緒的執行緒標識符

DWORDWINAPIGetCurrentThreadId(void);

7.返回當前執行緒的執行緒句柄

HANDLEWINAPIGetCurrentThread(void);

6. Java執行緒

java執行緒示意java執行緒示意
1)最簡單的情況是,Thread/Runnable的run()方法運行完畢,自行終止。
2)對於更複雜的情況,比如有循環,則可以增加終止標記變數和任務終止的檢查點。
3)最常見的情況,也是為了解決阻塞不能執行檢查點的問題,用中斷來結束執行緒,但中斷只是請求,並不能完全保證執行緒被終止,需要執行執行緒協同處理。[6]
4)IO阻塞和等鎖情況下需要通過特殊方式進行處理。
5)使用Future類的cancel()方法調用。
6)調用執行緒池執行器的shutdown()和shutdownNow()方法。
7)守護執行緒會在非守護執行緒都結束時自動終止。
8)Thread的stop()方法,但已不推薦使用。

守護執行緒

守護執行緒是特殊的執行緒,一般用於在後台為其他執行緒提供服務.
Java中,isDaemon():判斷一個執行緒是否為守護執行緒.
Java中,setDaemon():設定一個執行緒為守護執行緒.
C#守護執行緒
/**
* 本執行緒設定了一個逾時時間
* 該執行緒開始運行後,經過指定逾時時間,
* 該執行緒會拋出一個未檢查異常通知調用該執行緒的程式逾時
* 在逾時結束前可以調用該類的cancel方法取消計時
* @author solonote
*/
public class TimeoutThread extends Thread{
/**
* 計時器逾時時間
*/
private long timeout;
/**
* 計時是否被取消
*/
private boolean isCanceled = false;
/**
* 當計時器逾時時拋出的異常
*/
private TimeoutException timeoutException;
/**
* 構造器
* @param timeout 指定逾時的時間
*/
public TimeoutThread(long timeout,TimeoutException timeoutErr) {
super();
this.timeout = timeout;
this.timeoutException = timeoutErr;
//設定本執行緒為守護執行緒
this.setDaemon(true);
}
/**
* 取消計時
*/
public synchronized void cancel()
{
isCanceled = true;
}
/**
* 啟動逾時計時器
*/
public void run()
{
try {
Thread.sleep(timeout);
if(!isCanceled)
throw timeoutException;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

相關搜尋

熱門詞條

聯絡我們