轉自 http://blog.csdn.net/zsf8701/article/details/7842392
//線程屬性結構如下:
typedef struct
{
int etachstate; //線程的分離狀態
int schedpolicy; //線程調度策略
structsched_param schedparam; //線程的調度參數
int inheritsched; //線程的繼承性
int scope; //線程的範圍
size_t guardsize; //線程棧末尾的警戒緩衝區大小
int stackaddr_set; //線程的棧設定
void* stackaddr; //線程棧的位置
size_t stacksize; //線程棧的大小
}pthread_attr_t;
屬性值不能直接設定,須使用相關函數進行操作,初始化的函數為pthread_attr_init,這個函數必須在pthread_create函數之前調用。之後須用pthread_attr_destroy函數來釋放資源。線程屬性主要包括如下屬性:範圍(scope)、棧尺寸(stack size)、棧地址(stack address)、優先順序(priority)、分離的狀態(detached state)、調度策略和參數(scheduling policy and parameters)。預設的屬性為非綁定、非分離、預設1M的堆棧、與父進程同樣層級的優先順序。 一、線程的範圍(scope)
範圍屬性描述特定線程將與哪些線程競爭資源。線程可以在兩種競爭域內競爭資源: 進程域(process scope):與同一進程內的其他線程。 系統域(system scope):與系統中的所有線程。一個具有系統域的線程將與整個系統中所有具有系統域的線程按照優先順序競爭處理器資源,進行調度。 Solaris系統,實際上,從 Solaris 9 發行版開始,系統就不再區分這兩個範圍。 二、線程的綁定狀態(binding state)
輕進程(LWP:Light Weight Process)關於線程的綁定,牽涉到另外一個概念:輕進程(LWP:Light Weight Process):輕進程可以理解為核心線程,它位於使用者層和系統層之間。系統對線程資源的分配、對線程的控制是通過輕進程來實現的,一個輕進程可以控制一個或多個線程。 非綁定狀態
預設狀況下,啟動多少輕進程、哪些輕進程來控制哪些線程是由系統來控制的,這種狀況即稱為非綁定的。 綁定狀態
綁定狀況下,則顧名思義,即某個線程固定的"綁"在一個輕進程之上。被綁定的線程具有較高的響應速度,這是因為CPU時間片的調度是面向輕進程的,綁定的線程可以保證在需要的時候它總有一個輕進程可用。通過設定被綁定的輕進程的優先順序和調度級可以使得綁定的線程滿足諸如即時反應之類的要求。 三、線程的分離狀態(detached state) 線程的分離狀態決定一個線程以什麼樣的方式來終止自己。 非分離狀態
線程的預設屬性是非分離狀態,這種情況下,原有的線程等待建立的線程結束。只有當pthread_join()函數返回時,建立的線程才算終止,才能釋放自己佔用的系統資源。 分離狀態
分離線程沒有被其他的線程所等待,自己運行結束了,線程也就終止了,馬上釋放系統資源。應該根據自己的需要,選擇適當的分離狀態。 線程分離狀態的函數:pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。
第二個參數可選為PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。
這裡要注意的一點是,如果設定一個線程為分離線程,而這個線程運行又非常快,它很可能在pthread_create函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用,這樣調用pthread_create的線程就得到了錯誤的線程號。要避免這種情況可以採取一定的同步措施,最簡單的方法之一是可以在被建立的線程裡調用pthread_cond_timewait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回。設定一段等待時間,是在多線程編程裡常用的方法。但是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。還可以使用pthread_detach函數來設定分離線程( 子線程pthread_detach( pthread_self()) 或者父線程調用 pthread_detach(thread_id)(非阻塞,可立即返回) ) 四、線程的優先順序(priority) 新線程的優先順序為預設為0。 新線程不繼承父線程調度優先順序(PTHREAD_EXPLICIT_SCHED) 僅當調度策略為即時(即SCHED_RR或SCHED_FIFO)時才有效,並可以在運行時通過pthread_setschedparam()函數來改變,預設為0。
五、線程的棧地址(stack address) POSIX.1定義了兩個常量_POSIX_THREAD_ATTR_STACKADDR 和_POSIX_THREAD_ATTR_STACKSIZE檢測系統是否支援棧屬性。 也可以給sysconf函數傳遞_SC_THREAD_ATTR_STACKADDR或 _SC_THREAD_ATTR_STACKSIZE來進行檢測。 當進程棧地址空間不夠用時,指定建立線程使用由malloc分配的空間作為自己的棧空間。通過pthread_attr_setstackaddr和pthread_attr_getstackaddr兩個函數分別設定和擷取線程的棧地址。傳給pthread_attr_setstackaddr函數的地址是緩衝區的低地址(不一定是棧的開始地址,棧可能從高地址往低地址增長)。 六、線程的棧大小(stack size) 當系統中有很多線程時,可能需要減小每個線程棧的預設大小,防止進程的地址空間不夠用 當線程調用的函數會分配很大的局部變數或者函數調用層次很深時,可能需要增大線程棧的預設大小。 函數pthread_attr_getstacksize和 pthread_attr_setstacksize提供設定。 七、線程的棧保護區大小(stack guard size) 線上程棧頂留出一段空間,防止棧溢出。 當棧指標進入這段保護區時,系統會發出錯誤,通常是發送訊號給線程。 該屬性預設值是PAGESIZE大小,該屬性被設定時,系統會自動將該屬性大小補齊為頁大小的整數倍。 當改變棧地址屬性時,棧保護區大小通常清零。 八、線程的調度策略(schedpolicy)
POSIX標準指定了三種調度策略:先入先出策略 (SCHED_FIFO)、迴圈策略 (SCHED_RR) 和自訂策略 (SCHED_OTHER)。SCHED_FIFO 是基於隊列的發送器,對於每個優先順序都會使用不同的隊列。SCHED_RR 與 FIFO 相似,不同的是前者的每個線程都有一個執行時間配額。SCHED_FIFO 和 SCHED_RR 是對 POSIX Realtime 的擴充。SCHED_OTHER 是預設的調度策略。 新線程預設使用 SCHED_OTHER 調度策略。線程一旦開始運行,直到被搶佔或者直到線程阻塞或停止為止。 SCHED_FIFO
如果調用進程具有有效使用者識別碼 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM) 的先入先出線程屬於即時 (RT) 調度類。如果這些線程未被優先順序更高的線程搶佔,則會繼續處理該線程,直到該線程放棄或阻塞為止。對於具有進程爭用範圍 (PTHREAD_SCOPE_PROCESS)) 的線程或其調用進程沒有有效使用者識別碼 0 的線程,請使用 SCHED_FIFO,SCHED_FIFO 基於 TS 調度類。 SCHED_RR
如果調用進程具有有效使用者識別碼 0,則爭用範圍為系統 (PTHREAD_SCOPE_SYSTEM)) 的迴圈線程屬於即時 (RT) 調度類。如果這些線程未被優先順序更高的線程搶佔,並且這些線程沒有放棄或阻塞,則在系統確定的時間段內將一直執行這些線程。對於具有進程爭用範圍 (PTHREAD_SCOPE_PROCESS) 的線程,請使用 SCHED_RR(基於 TS 調度類)。此外,這些線程的調用進程沒有有效使用者識別碼 0。 九、線程並行層級(concurrency) ---- 不清楚
應用程式使用 pthread_setconcurrency() 通知系統其所需的並發層級。
轉自http://blog.csdn.net/cywosp/article/details/26469435
二、一次性初始化 在講解線程特有資料之前,先讓我們來瞭解一下一次性初始化。多線程程式有時有這樣的需求:不管建立多少個線程,有些資料的初始化只能發生一次。列如:在C++程式中某個類在整個進程的生命週期內只能存在一個執行個體對象,在多線程的情況下,為了能讓該對象能夠安全的初始化,一次性初始化機制就顯得尤為重要了。——在設計模式中這種實現常常被稱之為單例模式(Singleton)。Linux中提供了如下函數來實現一次性初始化: #include <pthread.h>
// Returns 0 on success, or a positive error number on error int pthread_once (pthread_once_t *once_control, void (*init) (void)); 利用參數once_control的狀態,函數pthread_once()可以確保無論有多少個線程調用多少次該函數,也只會執行一次由init所指向的由調用者定義的函數。init所指向的函數沒有任何參數,形式如下: void init (void) { // some variables initializtion in here } 另外,參數once_control必須是pthread_once_t類型變數的指標,指向初始化為PTHRAD_ONCE_INIT的靜態變數。在C++0x以後提供了類似功能的函數std::call_once (),用法與該函數類似。使用執行個體請參考https://github.com/ApusApp/Swift/blob/master/swift/base/singleton.hpp實現。
三、線程局部資料API 在Linux中提供了如下函數來對線程局部資料進行操作 #include <pthread.h>
// Returns 0 on success, or a positive error number on error int pthread_key_create (pthread_key_t *key, void (*destructor)(void *));//最好在pthread_once裡面調用,返回一個全域的的key,給所有線程共用。這樣在多線程下 這個key就只被建立一次
// Returns 0 on success, or a positive error number on error int pthread_key_delete (pthread_key_t key);
// Returns 0 on success, or a positive error number on error int pthread_setspecific (pthread_key_t key, const void *value);
// Returns pointer, or NULL if no thread-specific data is associated with key void *pthread_getspecific (pthread_key_t key);
函數pthread_key_create()為線程局部資料建立一個新鍵,並通過key指向新建立的鍵緩衝區。因為所有線程都可以使用返回的新鍵,所以參數key可以是一個全域變數(在C++多線程編程中一般不使用全域變數,而是使用單獨的類對線程局部資料進行封裝,每個變數使用一個獨立的pthread_key_t)。destructor所指向的是一個自訂的函數,其格式如下: void Dest (void *value) { // Release storage pointed to by 'value' } 只要線程終止時與key關聯的值不為NULL,則destructor所指的函數將會自動被調用。如果一個線程中有多個線程局部儲存變數,那麼對各個變數所對應的destructor函數的調用順序是不確定的,因此,每個變數的destructor函數的設計應該相互獨立。
函數pthread_key_delete()並不檢查當前是否有線程正在使用該線程局部資料變數,也不會調用清理函數destructor,而只是將其釋放以供下一次調用pthread_key_create()使用。在Linux線程中,它還會將與之相關的線程資料項目設定為NULL。 由於系統對每個進程中pthread_key_t類型的個數是有限制的,所以進程中並不能建立無限個的pthread_key_t變數。Linux中可以通過PTHREAD_KEY_MAX(定義於limits.h檔案中)或者系統調用sysconf(_SC_THREAD_KEYS_MAX)來確定當前系統最多支援多少個鍵。Linux中預設是1024個鍵,這對於大多數程式來說已經足夠了。如果一個線程中有多個線程局部儲存變數,通常可以將這些變數封裝到一個資料結構中,然後使封裝後的資料結構與一個線程局部變數相關聯,這樣就能減少對索引值的使用。
函數pthread_setspecific()用於將value的副本儲存於一資料結構中,並將其與調用線程以及key相關聯。參數value通常指向由調用者分配的一塊記憶體,當線程終止時,會將該指標作為參數傳遞給與key相關聯的destructor函數。當線程被建立時,會將所有的線程局部儲存變數初始化為NULL,因此第一次使用此類變數前必須先調用pthread_getspecific()函數來確認是否已經於對應的key相關聯,如果沒有,那麼pthread_getspecific()會分配一塊記憶體並通過pthread_setspecific()函數儲存指向該記憶體塊的指標。 參數value的值也可以不是一個指向調用者分配的記憶體地區,而是任何可以強制轉換為void*的變數值,在這種情況下,先前的pthread_key_create()函數應將參數 destructor設定為NULL 函數pthread_getspecific()正好與pthread_setspecific()相反,其是將pthread_setspecific()設定的value取出。在使用取出的值前最好是將void*轉換成未經處理資料類型的指標。 四、深入理解線程局部儲存機制 1. 深入理解線程局部儲存的實現有助於對其API的使用。在典型的實現中包含以下數組: 一個全域(進程層級)的數組,用於存放線程局部儲存的索引值資訊 pthread_key_create()返回的pthread_key_t類型值只是對全域數組的索引,該全域數組標記為pthread_keys,其格式大概如下:
數組的每個元素都是一個包含兩個欄位的結構,第一個欄位標記該數組元素是否在用,第二個欄位用於存放針對此鍵、線程局部儲存變的解構函數的一個副本,即destructor函數。 每個線程還包含一個數組,存有為每個線程分配的線程特有資料區塊的指標(通過調用pthread_setspecific()函數來儲存的指標,即參數中的value) 2. 在常見的儲存pthread_setspecific()函數參數value的實現中,大多數都類似於下圖的實現。圖中假設pthread_keys[1]分配給func1()函數,pthread API為每個函數維護指向線程局部儲存資料區塊的一個指標數組,其中每個數組元素都與圖線程局部資料鍵的實現(上圖)中的全域pthread_keys中元素一一對應。
五、總結 使用全域變數或者靜態變數是導致多線程編程中非安全執行緒的常見原因。在多線程程式中,保障非安全執行緒的常用手段之一是使用互斥鎖來做保護,這種方法帶來了並發效能下降,同時也只能有一個線程對資料進行讀寫。如果程式中能避免使用全域變數或靜態變數,那麼這些程式就是安全執行緒的,效能也可以得到很大的提升。如果有些資料只能有一個線程可以訪問,那麼這一類資料就可以使用線程局部儲存機制來處理,雖然使用這種機制會給程式執行效率上帶來一定的影響,但對於使用鎖機制來說,這些效能影響將可以忽略。Linux C++的線程局部儲存簡單實現可參考https://github.com/ApusApp/Swift/blob/master/swift/base/threadlocal.h,更詳細且高效的實現可參考Facebook的folly庫中的ThreadLocal實現。更高效能的線程局部儲存機制就是使用__thread,這將在下一節中討論。 --------------------------------------------------------------------------------
轉載請說明出處:http://blog.csdn.net/cywosp/article/details/26876231
Linux中的線程局部儲存(二)
在Linux中還有一種更為高效的線程局部儲存方法,就是使用關鍵字__thread來定義變數。__thread是GCC內建的線程局部儲存設施(Thread-Local Storage),它的實現非常高效,與pthread_key_t向比較更為快速,其儲存效能可以與全域變數相媲美,而且使用方式也更為簡單。建立線程局部變數只需簡單的在全域或者靜態變數的聲明中加入__thread說明即可。列如: static __thread char t_buf[32] = {'\0'}; extern __thread int t_val = 0; 凡是帶有__thread的變數,每個線程都擁有該變數的一份拷貝,且互不干擾。線程局部儲存中的變數將一直存在,直至線程終止,當線程終止時會自動釋放這一儲存。__thread並不是所有資料類型都可以使用的,因為其只支援POD(Plain old data structure)[1]類型,不支援class類型——其不能自動調用建構函式和解構函式。同時__thread可以用於修飾全域變數、函數內的靜態變數,但是不能用於修飾函數的局部變數或者class的普通成員變數。另外,__thread變數的初始化只能用編譯期常量,例如: __thread std::string t_object_1 ("Swift"); // 錯誤,因為不能調用對象的建構函式 __thread std::string* t_object_2 = new std::string (); // 錯誤,初始化必須用編譯期常量 __thread std::string* t_object_3 = nullptr; // 正確,但是需要手工初始化並銷毀對象
除了以上之外,關於線程局部儲存變數的聲明和使用還需注意一下幾點: 如果變數聲明中使用量關鍵字static或者extern,那麼關鍵字__thread必須緊隨其後。 與一般的全域變數或靜態變數一樣,線程局部變數在聲明時可以設定一個初始化值。