歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

linux線程的簡介

1、線程的概述
線程,有時被稱為輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。一個標准的線程由線程ID,當前指令指針(PC),寄存器集合和堆棧組成。另外,線程是進程中的一個實體,是被系統獨立調度和分派的基本單位,線程自己不擁有系統資源,只擁有一點兒在運行中必不可少的資源,但它可與同屬一個進程的其它線程共享進程所擁有的全部資源。一個線程可以創建和撤消另一個線程,同一進程中的多個線程之間可以並發執行。由於線程之間的相互制約,致使線程在運行中呈現出間斷性。線程也有就緒、阻塞和運行三種基本狀態。就緒狀態是指線程具備運行的所有條件,邏輯上可以運行,在等待處理機;運行狀態是指線程占有處理機正在運行;阻塞狀態是指線程在等待一個事件(如某個信號量),邏輯上不可執行。每一個程序都至少有一個線程,若程序只有一個線程,那就是程序本身。線程是程序中一個單一的順序控制流程。進程內一個相對獨立的、可調度的執行單元,是系統獨立調度和分派CPU的基本單位指運行中的程序的調度單位。在單個程序中同時運行多個線程完成不同的工作,稱為多線程。
2、線程的基本操作
1、進程的創建
首先在Linux編寫多線程程序需要包含頭文件pthread.h。如果想要產生一個線程需要調用API接口pthread_create()。第一個參數為指向線程標識符的指針,第二個參數用來設置線程屬性,第三個參數是線程運行函數的起始地址,最後一個參數是運行函數的參數。如果線程創建成功這個接口就會返回0,如果創建線程失敗,返回的是錯誤編號。
pthread_creat函數在創建線程時由內核向新線程傳遞參數,如果需要傳遞多個參數則需要將所有參數組織在一個結構體內,再將結構體的地址作為參數傳給新線程。例如下面這個程序:

2、線程的屬性
  上面我用pthread_create函數創建了一個線程,在這個線程中,我使用了默認屬性,即將該函數的第二個參數設為NULL。我們還是有必要來了解一下線程的有關屬性:屬性結構為pthread_attr_t,它同樣在頭文件/usr/include/pthread.h中定義。屬性值不能直接設置,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。屬性對象主要包括是否綁定、是否分離、堆棧地址、堆棧大小、優先級。默認的屬性為非綁定、非分離、缺省1M的堆棧、與父進程同樣級別的優先級。
關於線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process)。輕進程可以理解為內核線程,它位於用戶層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。默認狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱為非綁定的。綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設置被綁定的輕進程的優先級和調度級可以使得綁定的線程滿足諸如實時反應之類的要求。
3、線程訪問數據
和進程相比,線程的最大優點之一是數據的共享性,各個進程共享父進程處沿襲的數據段,可以方便的獲得、修改數據。但這也給多線程編程帶來了許多問題。我們必須當心有多個不同的進程訪問相同的變量。許多函數是不可重入的,即同時不能運行一個函數的多個拷貝(除非使用不同的數據段)。在函數中聲明的靜態變量常常帶來問題,函數的返回值也會有問題。因為如果返回的是函數內部靜態聲明的空間的地址,則在一個線程調用該函數得到地址後使用該地址指向的數據時,別的線程可能調用此函數並修改了這一段數據。在進程中共享的變量必須用關鍵字volatile來定義,這是為了防止編譯器在優化時(如gcc中使用-OX參數)改變它們的使用方式。為了保護變量,我們必須使用信號量、互斥等方法來保證我們對變量的正確使用。
在單線程的程序裡,有兩種基本的數據:全局變量和局部變量。但在多線程程序裡,還有第三種數據類型:線程數據(TSD: Thread-Specific Data)。它和全局變量很象,在線程內部,各個函數可以象使用全局變量一樣調用它,但它對線程外部的其它線程是不可見的。這種數據的必要性是顯而易見的。例如我們常見的變量errno,它返回標准的出錯信息。它顯然不能是一個局部變量,幾乎每個函數都應該可以調用它;但它又不能是一個全局變量,否則在A線程裡輸出的很可能是B線程的出錯信息。要實現諸如此類的變量,我們就必須使用線程數據。我們為每個線程數據創建一個鍵,它和這個鍵相關聯,在各個線程裡,都使用這個鍵來指代線程數據,但在不同的線程裡,這個鍵代表的數據是不同的,在同一個線程裡,它代表同樣的數據內容。
4.終止線程
線程的退出方式有三種:
(1)線程體函數從啟動線程中返回,return
(2)線程被另一個線程終止。類似於一個進程被另一個進程調用kill函數殺死
(3)線程自行調用pthread_exit()退出。pthread_exit 可以在退出的時候傳遞一些信息,這些信息可以用 pthread_join 函數獲得。exit()是進程退出,如果在線程函數中調用exit,那該線程的進程也就會退出。會導致該線程所在進程的其他線程退出。慎重使用exit()。
3、線程的同步
一、互斥鎖
在編程中,引入了對象互斥鎖的概念,來保證共享數據操作的完整性。每個對象都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個線程訪問該對象。
1)初始化互斥鎖
函數原型:int pthread_mutex_init(pthread_mutex_t *mp, const pthread_mutexattr_t *mattr);參數說明:mp是互斥鎖地址,mattr是屬性,通常默認 null初始化互斥鎖之前,必須將其所在的內存清零。如果互斥鎖已初始化,則它會處於未鎖定狀態。互斥鎖可以位於進程之間共享的內存中或者某個進程的專用內存中。
2)鎖定互斥鎖
函數原型:int pthread_mutex_lock(pthread_mutex_t *mutex);函數說明:當 pthread_mutex_lock() 返回時,該互斥鎖已被鎖定。調用線程是該互斥鎖的屬主。如果該互斥鎖已被另一個線程鎖定和擁有,則調用線程將阻塞,直到該互斥鎖變為可用為止。
如果互斥鎖類型為 PTHREAD_MUTEX_NORMAL,則不提供死鎖檢測。嘗試重新鎖定互斥鎖會導致死鎖。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或未鎖定,則將產生不確定的行為。
如果互斥鎖類型為 PTHREAD_MUTEX_ERRORCHECK,則會提供錯誤檢查。如果某個線程嘗試重新鎖定的互斥鎖已經由該線程鎖定,則將返回錯誤。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤。
如果互斥鎖類型為 PTHREAD_MUTEX_RECURSIVE,則該互斥鎖會保留鎖定計數這一概念。線程首次成功獲取互斥鎖時,鎖定計數會設置為 1。線程每重新鎖定該互斥鎖一次,鎖定計數就增加 1。線程每解除鎖定該互斥鎖一次,鎖定計數就減小 1。 鎖定計數達到 0 時,該互斥鎖即可供其他線程獲取。如果某個線程嘗試解除鎖定的互斥鎖不是由該線程鎖定或者未鎖定,則將返回錯誤。
如果互斥鎖類型是 PTHREAD_MUTEX_DEFAULT,則嘗試以遞歸方式鎖定該互斥鎖將產生不確定的行為。對於不是由調用線程鎖定的互斥鎖,如果嘗試解除對它的鎖定,則會產生不確定的行為。如果嘗試解除鎖定尚未鎖定的互斥鎖,則會產生不確定的行為。
返回值:pthread_mutex_lock() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗並返回對應的值。
EAGAIN:由於已超出了互斥鎖遞歸鎖定的最大次數,因此無法獲取該互斥鎖。
EDEADLK:當前線程已經擁有互斥鎖。
3)解除鎖定互斥鎖
函數原型:int pthread_mutex_unlock(pthread_mutex_t *mutex); 函數說明:pthread_mutex_unlock() 可釋放 mutex 引用的互斥鎖對象。互斥鎖的釋放方式取決於互斥鎖的類型屬性。如果調用 pthread_mutex_unlock() 時有多個線程被 mutex 對象阻塞,則互斥鎖變為可用時調度策略可確定獲取該互斥鎖的線程。對於 PTHREAD_MUTEX_RECURSIVE 類型的互斥鎖,當計數達到零並且調用線程不再對該互斥鎖進行任何鎖定時,該互斥鎖將變為可用。
返回值:pthread_mutex_unlock() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗並返回對應的值。
EPERM :當前線程不擁有互斥鎖。
4)銷毀互斥鎖
函數原型:int pthread_mutex_destroy(pthread_mutex_t *mp);請注意,沒有釋放用來存儲互斥鎖的空間。
返回值:pthread_mutex_destroy() 在成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下任一情況,該函數將失敗並返回對應的值。
EINVAL: mp 指定的值不會引用已初始化的互斥鎖對象。
二、條件變量(cond)
與互斥鎖不同,條件變量是用來等待而不是用來上鎖的。條件變量用來自動阻塞一個線程,直到某特殊情況發生為止。通常條件變量和互斥鎖同時使用。條件變量分為兩部分: 條件和變量。條件本身是由互斥量保護的。線程在改變條件狀態前先要鎖住互斥量。條件變量使我們可以睡眠等待某種條件出現。條件變量是利用線程間共享的全局變量進行同步的一種機制,主要包括兩個動作:一個線程等待"條件變量的條件成立"而掛起;另一個線程使"條件成立"(給出條件成立信號)。條件的檢測是在互斥鎖的保護下進行的。如果一個條件為假,一個線程自動阻塞,並釋放等待狀態改變的互斥鎖。如果另一個線程改變了條件,它發信號給關聯的條件變量,喚醒一個或多個等待它的線程,重新獲得互斥鎖,重新評價條件。如果兩進程共享可讀寫的內存,條件變量可以被用來實現這兩進程間的線程同步。
1)初始化條件變量
靜態初始化:pthread_cond_t cond = PTHREAD_COND_INITIALIER;
動態初始化:ead_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr);
2)等待條件成立
釋放鎖,同時阻塞等待條件變量為真才行。timewait()設置等待時間,仍未signal,返回ETIMEOUT(加鎖保證只有一個線程wait)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
int thread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);
3)激活條件變量
pthread_cond_signal,pthread_cond_broadcast(激活所有等待線程)
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond); 解除所有線程的阻塞
4)清除條件變量
無線程等待,否則返回EBUSY,int pthread_cond_destroy(pthread_cond_t *cond);
三、信號量
如同進程一樣,線程也可以通過信號量來實現通信,雖然是輕量級的。信號量函數的名字都以"sem_"打頭。線程使用的基本信號量函數有四個。
1)信號量初始化
int sem_init (sem_t *sem , int pshared, unsigned int value);
這是對由sem指定的信號量進行初始化,設置好它的共享選項(linux 只支持為0,即表示它是當前進程的局部信號量),然後給它一個初始值VALUE。
2)等待信號量
信號量減1,然後等待直到信號量的值大於0。int sem_wait(sem_t *sem);
3)釋放信號量
信號量值加1。並通知其他等待線程。int sem_post(sem_t *sem);
4)銷毀信號量
使用信號量後都要將它進行清理。歸還占有的一切資源。int sem_destroy(sem_t *sem);
Copyright © Linux教程網 All Rights Reserved