thread

thread

計算機科學術語
線程(thread, 台灣稱執行緒)是"進程"中某個單一順序的控制流。也被稱為輕量進程(lightweight processes)。計算機科學術語,指運行中的程序的調度單位。
    中文名:線程 外文名:thread 所屬學科:計算機

概念

一般來說,我們把正在計算機中執行的程序叫做“進程”(process) ,而不将其稱為“程序”(program)。所謂“線程”(thread),是“進程”中某個單一順序的控制流。新興的操作系統,如Mac、Windows NT、Windows 95等,大多采用多線程的概念,把線程視為基本執行單位。

甚至最簡單的Applet也是由多個線程來完成的。在Java中,任何一個Applet的paint()和update()方法都是由AWT(Abstract Window Toolkit)繪圖與事件處理線程調用的,而Applet 主要的裡程碑方法——init(),start(),stop()和destory()——是由執行該Applet的應用調用的。

單線程的概念沒有什麼新的地方,真正有趣的是在一個程序中同時使用多個線程來完成不同的任務。某些地方用輕量進程(Lightweig ht Process)來代替線程,線程與真正進程的相似性在于它們都是單一順序控制流。然而線程被認為輕量是由于它運行于整個程序的上下文内,能使用整個程序共有的資源和程序環境。

作為單一順序控制流,在運行的程序内線程必須擁有一些資源作為必要的開銷。例如,必須有執行堆棧和程序計數器。在線程内執行的代碼隻在它的上下文中起作用,因此某些地方用"執行上下文"來代替"線程"。

屬性

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

(1)線程體

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

(2)線程狀态

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

新線程态(New Thread)

可運行态(Runnable)

非運行态(Not Runnable)

死亡态(Dead)

(3)線性優先級

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

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

(4)幽靈線程

任何一個Java線程都能成為幽靈線程。它是作為運行于同一個進程内的對象和線程的服務提供者。例如,HotJava浏覽器有一個稱為" 後台圖片閱讀器"的幽靈線程,它為需要圖片的對象和線程從文件系統或網絡讀入圖片。

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

(5)線程組

每個Java線程都是某個線程組的成員。線程組提供一種機制,使得多個線程集于一個對象内,能對它們實行整體操作。譬如,你能用一個方法調用來啟動或挂起組内的所有線程。Java線程組由ThreadGroup類實現。

當線程産生時,可以指定線程組或由實時系統将其放入某個缺省的線程組内。線程隻能屬于一個線程組,并且當線程産生後不能改變它所屬的線程組。

程序

對于多線程的好處這就不多說了。但是,它同樣也帶來了某些新的麻煩。隻要在設計程序時特别小心留意,克服這些麻煩并不算太困難。

(1)同步線程

許多線程在執行中必須考慮與其他線程之間共享數據或協調執行狀态。這就需要同步機制。在Java中每個對象都有一把鎖與之對應。但Java不提供單獨的lock和unlock操作。它由高層的結構隐式實現, 來保證操作的對應。(然而,我們注意到Java虛拟機提供單獨的monito renter和monitorexit指令來實現lock和unlock操作。)

synchronized語句計算一個對象引用,試圖對該對象完成鎖操作, 并且在完成鎖操作前停止處理。當鎖操作完成synchronized語句體得到執行。當語句體執行完畢(無論正常或異常),解鎖操作自動完成。作為面向對象的語言,synchronized經常與方法連用。一種比較好的辦法是,如果某個變量由一個線程賦值并由别的線程引用或賦值,那麼所有對該變量的訪問都必須在某個synchromized語句或synchronized方法内。

現在假設一種情況:線程1與線程2都要訪問某個數據區,并且要求線程1的訪問先于線程2, 則這時僅用synchronized是不能解決問題的。這在Unix或Windows NT中可用Simaphore來實現。而Java并不提供。在Java中提供的是wait()和notify()機制。使用如下:

synchronized method_1(…) { // call by thread 1.

∥access data area;

available=true;

notify();

}

synchronized method_2(…){ ∥call by thread 2.

while(!available) {

try{

wait();∥wait for notify().

} catch (Interrupted Exception

}

∥access data area

}

}

其中available是類成員變量,置初值為false。

如果在method-2中檢查available為假,則調用wait()。wait()的作用是使線程2進入非運行态,并且解鎖。在這種情況下,method-1可以被線程1調用。當執行notify()後。線程2由非運行态轉變為可運行态。當method-1調用返回後。線程2可重新對該對象加鎖,加鎖成功後執行wait()返回後的指令。這種機制也能适用于其他更複雜的情況。

(2)死鎖

如果程序中有幾個競争資源的并發線程,那麼保證均衡是很重要的。系統均衡是指每個線程在執行過程中都能充分訪問有限的資源。系統中沒有餓死和死鎖的線程。Java并不提供對死鎖的檢測機制。對大多數的Java程序員來說防止死鎖是一種較好的選擇。最簡單的防止死鎖的方法是對競争的資源引入序号,如果一個線程需要幾個資源,那麼它必須先得到小序号的資源,再申請大序号的資源。

比較

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

與進程相對應,線程與資源分配無關,它屬于某一個進程,并與進程内的其他線程一起共享進程的資源。當進程發生調度時,不同的進程擁有不同的虛拟地址空間,而同一進程内的不同線程共享同一地址空間。

線程隻由相關堆棧(系統棧或用戶棧)寄存器和線程控制表TCB組成。寄存器可被用來存儲線程内的局部變量,但不能存儲其他線程的相關變量。

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

多線程與進程之間的關系

适用範圍

典型的應用

服務器中的文件管理或通信控制前後台處理異步處理

執行特性

一個線程必須處于如下四種可能的狀态:

初始:一個線程調用了new方法之後,并在調用start方法之前的所處狀态。在初始态中,可以調用start和stop方法。 Runnable:一旦線程調用了start 方法,線程就轉到Runnable 狀态,注意,如果線程處于Runnable狀态,它也有可能不在運行,這是因為還有優先級和調度問題。 阻塞/ NonRunnable:線程處于阻塞/NonRunnable狀态,這是由兩種可能性造成的:要麼是因挂起而暫停的,要麼是由于某些原因而阻塞的,例如包括等待IO請求的完成。退出:線程轉到退出狀态,這有兩種可能性,要麼是run方法執行結束,要麼是調用了stop方法。

最後一個概念就是線程的優先級,線程可以設定優先級,高優先級的線程可以安排在低優先級線程之前完成。一個應用程序可以通過使用線程中的方法setpriority(int),來設置線程的優先級大小。

線程有5種基本操作:

派生:線程在進程内派生出來,它即可由進程派生,也可由線程派生。阻塞(Block):如果一個線程在執行過程中需要等待某個事件發生,則被阻塞。激活(unblock):如果阻塞線程的事件發生,則該線程被激活并進入就緒隊列。調度(schedule):選擇一個就緒線程進入執行狀态。結束(Finish):如果一個線程執行結束,它的寄存器上下文以及堆棧内容等将被釋放。

線程的狀态與操作

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

分類

線程有兩個基本類型:

用戶級線程:管理過程全部由用戶程序完成,操作系統内核心隻對進程進行管理。系統級線程(核心級線程):由操作系統内核進行管理。操作系統内核給應用程序提供相應的系統調用和應用程序接口API,以使用戶程序可以創建、執行、撤消線程。

舉例

1. UnixInternational線程

Solaris操作系統使用的線程叫做Unix International線程,支持内核線程、輕權進程和用戶線程。一個進程可有大量用戶線程;大量用戶線程複用少量的輕權進程,輕權進程與内核線程一一對應。

用戶級線程在調用核心服務時(如文件讀寫),需要“捆綁(bound)”在一個lwp上。永久捆綁(一個LWP固定被一個用戶級線程占用,該LWP移到LWP池之外)和臨時捆綁(從LWP池中臨時分配一個未被占用的LWP)。

在調用系統服務時,如果所有LWP已被其他用戶級線程所占用(捆綁),則該線程阻塞直到有可用的LWP。

如果LWP執行系統線程時阻塞(如read()調用),則當前捆綁在LWP上的用戶級線程也阻塞。

用戶線程、輕權進程和核心線程的關系

Unix International線程的有關API

UNIX International 線程的頭文件是

1.創建用戶級線程

int thr_create ( void * stack_base, size_t stack_size, void *(*start_routine)(void *), void * arg, long flags, thread_t * new_thr);

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

2.等待用戶級線程

int thr_join (thread_t wait_for, thread_t *dead, void **status);

3.挂起用戶級線程

int thr_suspend (thread_t thr);

4.繼續用戶級線程

int thr_continue (thread_t thr);

5.退出用戶級線程

void thr_exit (void *status);

6.返回當前用戶級線程的線程标識符

thread_t thr_self ( void );

2.POSIX線程

Pthreads(POSIX Thread)的相關API。

Pthreads 線程的頭文件是

1.創建用戶級線程

int pthread_create(pthread_t * thread, const pthread_attr_t * attr, void *(*start_routine) (void *), void *arg);

2.等待用戶級線程

int pthread_join(pthread_t thread, void ** retval);

3.退出用戶級線程

void pthread_exit(void *retval);

4.返回當前用戶級線程的線程标識符

pthread_t pthread_self(void);

5.用戶級線程的取消

int pthread_cancel(pthread_t thread);

3.C++11線程

C++ 11 線程的頭文件是

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線程的頭文件是

C11線程僅僅是個“建議标準”,也就是說100%遵守C11标準的C編譯器是可以不支持C11線程的。根據C11标準的規定,隻要編譯器預定義了__STDC_NO_THREADS__宏,就可以沒有頭文件,自然也就也沒有下列函數。

1.創建用戶級線程

int thrd_create( thrd_t *thr, thrd_start_t func, void *arg );

2.結束本線程

_Noreturn void thrd_exit( int res );

3.等待用戶級線程運行完畢

int thrd_join( thrd_t thr, int *res );

4.返回當前線程的線程标識符

thrd_t thrd_current();

5. Win32線程

Win32線程的上下文包括:寄存器、核心棧、線程環境塊和用戶棧。

Win32線程狀态

就緒狀态:進程已獲得除處理機外的所需資源,等待執行。備用狀态:特定處理器的執行對象,系統中每個處理器上隻能有一個處于備用狀态的線程。運行狀态:完成描述表切換,線程進入運行狀态,直到内核搶先、時間片用完、線程終止或進行等待狀态。等待狀态:線程等待對象句柄,以同步它的執行。等待結束時,根據優先級進入運行、就緒狀态。轉換狀态:線程在準備執行而其内核堆棧處于外存時,線程進入轉換狀态;當其内核堆棧調回内存,線程進入就緒狀态。終止狀态:線程執行完就進入終止狀态;如執行體有一指向線程對象的指針,可将線程對象重新初始化,并再次使用。

Windows NT的線程狀态

Win32線程的有關API

Win32線程的頭文件是,僅适用于Windows操作系統。

1.創建用戶級線程

HANDLE WINAPI CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);

2.結束本線程

VOID WINAPI ExitThread(DWORD dwExitCode);

3.挂起指定的線程

DWORD WINAPI SuspendThread( HANDLE hThread );

4.恢複指定線程運行

DWORD WINAPI ResumeThread(HANDLE hThread);

5.等待線程運行完畢

DWORD WINAPI WaitForSingleObject(HANDLE hHandle, DWORD dwMilliseconds);

6.返回當前線程的線程标識符

DWORD WINAPI GetCurrentThreadId(void);

7.返回當前線程的線程句柄

HANDLE WINAPI GetCurrentThread(void);

6. Java線程

1)最簡單的情況是,Thread/Runnable的run()方法運行完畢,自行終止。

2)對于更複雜的情況,比如有循環,則可以增加終止标記變量和任務終止的檢查點。

3)最常見的情況,也是為了解決阻塞不能執行檢查點的問題,用中斷來結束線程,但中斷隻是請求,并不能完全保證線程被終止,需要執行線程協同處理。

4)IO阻塞和等鎖情況下需要通過特殊方式進行處理。

5)使用Future類的cancel()方法調用。

6)調用線程池執行器的shutdown()和shutdownNow()方法。

7)守護線程會在非守護線程都結束時自動終止。

8)Thread的stop()方法,但已不推薦使用。

相關詞條

相關搜索

其它詞條