Linux操作系統——線程

線程:在一個程序裏的一個執行路線就叫做線程。更準確的定義是:線程是一個進程內部的控制序列。
一切進程至少都有一個執行線程。
進程和線程:
進程是資源競爭的基本單位。
線程是程序執行的最小單位。、
線程共享進程數據,但也擁有自己的一部分數據:線程ID,一組寄存器,棧,errno,信號屏蔽字,調度優先級。
進程的多個線程共享
同一地址空間,因此Text Segment,Data Segment都是共享的,如果定義一個函數,在各線程中都可以訪問到,各線程中都以調用,如果定義一個全局變量,在各線程中都可以訪問到,初次之外,各線程還共享以下進程資源和環境。
文件描述符表
每種信號的處理方式(SIG_IGN.SIG——DFL或者自定義的信號處理函數)
當前工作目錄用戶id和組id
線程的優點:創建一個新線程的代價要比創建一個新進程小的多,與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多。
線程佔用的資源比進程少很多。
能充分利用多處理器的可並行數量。
在等待慢速I/O操作結束的同時,程序可執行其他的計算任務。
計算密集型應用,爲了能在多處理器系統上運行,將計算分解到多個線程中實現。
I/O密集型應用,爲了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
線程的缺點
性能損失:
一個很少被外部事件阻塞的計算密集型線程往往無法與共享它的線程共享同一個處理器。如果計算密集型線程的數量比可用的處理器多,那麼可能會有較大的性能損失,這裏的性能損失指的是增加了額外的同步和調度開銷,而可用的資源不變。
健壯性降低:
編寫多線程需要更全面更深入的考慮,在一個多線程程序裏,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。

缺乏訪問控制:進程是訪問控制的基本粒度在一個線程中調用某些OS函數會對真個進程造成影響。
編程難度提高:
編寫與調試一個多線程程序比單線程程序困難得多。
int pthread_create(pthreate_t thread,const pthread_attr_t *attr,void (start_rutine)(void),void *arg)
thread:返回線程ID
attr:設置線程屬性,attr爲NULL表示使用默認屬性。
start_routine:是個函數地址,線程啓動後要執行的函數。
arg:傳給線程啓動函數的參數
返回值:成功返回0,失敗返回錯誤碼。

錯誤檢查:傳統的一些函數是,成功返回0,失敗返回-1,並且對全局變量errno賦值以指示錯誤。
pthreads函數出錯時不會設置全局變量errno(而大部分其他POSIX函數會這樣做)而是將錯誤代碼通過返回值返回。
pthreads同樣也提供了線程內errno變量,以支持其它使用errno的代碼。對於pthreads函數的錯誤,建議通過返回值判定,因爲讀取返回值要比讀取線程內的errno變量的開銷更小。
進程ID和線程ID
在Linux中,目前的線程實現是Native POSIX Thread Libaray,簡稱NPTL。在這種實現下,線程又被稱爲輕量級進程,每一個用戶態的線程,在內核中都對應一個調度實體,也擁有自己的進程描述符。
沒有線程之前,一個進程對應內核裏的一個進程描述符,對應一個進程ID。但是引入線程概念之後,情況發生了變化,一個用戶進程下管理N個用戶態線程,每個線程作爲一個獨立的調度實體在內核態都有自己的進程描述符,進程和內核的描述符一下子就變成了1:N關係,POSIX標準又要求進程內的所有線程調用getpid函數時返回相同的進程ID。
多線程的進程,又被稱爲線程組,線程組內的每一個線程在內核之中都存在一個進程描述符與之對應。進程描述符結構體中的pid,表面上看對應的是進程ID,其實不然,它對應的是線程ID;進程描述符中的tgid,含義是Thread Group ID,該值對應的是用戶層面的進程ID
ps -L:顯示線程ID,線程組內線程的個數。
強調一點:線程和進程不一樣,進程有父進程的概念,但在線程組裏面,所有的線程都是對等關係。
線程ID及進程地址空間佈局
pthread_create函數會產生一個線程ID,存放在第一個參數指向的地址中。該線程ID和前面說的線程ID不是一回事。
前面講的線程ID屬於進程調度的範疇。因爲線程是輕量級進程,是操作系統調度器的最小單位,所以需要一個數值來唯一表示該線程。
pthread_create函數產生並標記在第一個參數指向的地址中的線程ID中。屬於NPTL線程庫的範疇。線程庫的後續操作,就是根據該線程ID來操作線程的。
線程庫NPTL提供了pthread_selt函數,可以獲得線程 自身的ID。
pthread_t pthread_self(void);
pthread_t類型的線程ID,本質就是一個進程地址空間上的一個地址。
線程終止:從線程函數return。這種方法對主線程不適用,從main函數return相當於調用exit.
線程可以調用pthread_cancel終止同一進程中的另一個線程。
pthread_exit函數
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一個局部比變量。
無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者。
需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因爲當其它線程得到這個返回指針時線程函數已經退出了。
int pthread_cancel(pthread_t thread);
取消一個執行中的線程。
成功返回0,失敗返回錯誤碼。
線程等待與分離
爲什麼需要線程等待?
已經退出的線程,其空間沒有釋放,仍然在進程的地址空間內。
創建新的線程不會複用剛纔退出線程的地址空間。
int pthread_join(pthread_t thread,void** value_ptr_
value_ptr:它指向一個指針,後者指向線程的返回值。
成功返回0,失敗返回錯誤碼。
調用該函數的線程將掛起等待,知道id爲thread的線程終止,thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的。
如果thread線程通過return 返回,value_ptr所指向的單元裏存放的是thread線程函數的返回值。
如果thread線程被別的線程嗲用pthread_cancel異常終止掉,value_ptr所指向的單元裏存放的是常數PTHREAD_CANCELED;
如果thread線程是自己調用pthread exit終止的,valueptr所指向的單元存放的是傳給pthread_exit的參數。
如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ptr參數。
分離線程
默認情況下,新創建的線程是joinable的,線程退出後,需要對其進行pthread_join操作,否則無法釋放資源 。從而造成系統泄漏。
如果不關心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統,當線程退出時,自動釋放線程資源。
int pthread_detach(pthread_t thread);
可以是線程組內其他線程對目標線程進行分離,也可以是線程自己分離。
pthread_detach(pthread_self());
joinable和分離是衝突的,一個線程不能即是joinable有時分離的。
線程同步與互斥
大部分情況,線程使用的數據都是局部變量,變量的地址空間在線程棧空間內,這種情況,變量歸屬單個 線程,其他線程無法獲得這種變量。
但有時候,很多變量都需要在線程間共享,這樣的變量稱爲共享變量,可以通過數據的共享,完成線程之間的交互。
多個線程併發的操作共享變量,會帶來一些問題。
互斥量的接口
初始化互斥量:
靜態分配:
pthread_mutex_t mutex=PTHREAD_MTEX_INITIALIZER
動態分配:int pthread_mutex_init(pthred_mutex_t restrict mutex,const pthread_mutexattr_t restrict attr);
mutexL:要初始化的互斥量
attr:NULL;
銷燬互斥量:使用PTHREAD_MUTEX_INITIALIZER初始化的互斥量不需要銷燬。
不要銷燬一個已經加鎖的互斥量。
已經銷燬的互斥量,要確保後面不會有線程再嘗試加鎖。
int pthread_mutex_destory(pthread_mutext_t *mutex);
互斥量加鎖和解鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
成功返回0,失敗返回錯誤號。
調用pthread_lock時,可能會遇到以下情況:
互斥量處於沒有鎖狀態,該函數會將互斥量鎖定,同時返回成功。
發起函數調用時,其他線程已經鎖定互斥量,或者存在其他線程同時申請互斥量,但沒有競爭到互斥量,那麼pthread_lock調用會陷入阻塞,等待互斥量解鎖。
條件變量:當一個線程互斥的訪問某個變量時,它可能發現在其它線程改變狀態之前,它什麼也做不了。
例如一個線程訪問隊列時,發現隊列爲空,它只能等待,直到其它線程將一個節點添加到隊列的這種情況就需要用到條件變量。
int ptread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
cond:要初始化的條件變量
attr:NULL
intpthread_cond_destroy(pthread_cond_t *cond)
等到條件滿足
int pthread_cond_wait(pthread_cond_t*restrict cond,pthread_mutext_t *restrictx);
cond:要在這個條件變量上等待。
mutex:互斥量。
喚醒等待:
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
爲什麼pthread_cond_wait需要互斥量?
條件等待是線程同步間同步的一種手段,如果只有一個線程,條件不滿足,一直等下去都不會滿足,所以必須要有一個線程通過某些操作,改變共享變量,使原先不滿足的條件變得滿足,並且友好的通知等待在條件變量上的線程。
條件不會無緣無故的突然變得滿足了,必然會牽扯到共享數據的變化。所以一定要用互斥鎖來保護。沒有互斥鎖就無法安全的獲取和修改共享數據。
POSIX信號量:POSIX信號量和SystemV信號量作用相同,都是用於同步操作,達到無衝突的訪問共享資源的目的。但可以用於線程間同步。
初始化信號量

#include<semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
pshared:0 表示線程間共享,非0表示進程間共享。
value:信號量初始值
int sem_destroy(sem_t *sem)
銷燬信號量
等待信號量
int sem_wait(sem_t *sem);
發佈信號量
int sem_post(sem_t *sem);
讀寫鎖:在編寫多線程的時候,有一種情況是十分常見的,那就是,有些公共數據修改的機會比較少,相比較改寫,它們讀的機會反而高的多。通常而言,在讀的過程中,往往伴隨着查找操作,中間耗時很長,給這種代碼段加鎖,會極大的降低我們程序的效率。那麼有沒有一種方法,可以專門處理這種多讀少寫的情況,那就是讀寫鎖。
注意:寫獨佔,讀共享,寫鎖優先級高。
讀寫鎖接口:
int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock,const pthread_rwlock_t *restrict attr);
銷燬:
int pthread_rwlock_destory(pthread_rwlock_t *rwlock);
加鎖和解鎖
int pthread_rwlock_rdlock(pthread_rwlock_t*rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t*rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t &rwlock);

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章