linux線程庫
① linux系統中線程同步實現機制有哪些
LinuxThread的線程機制
LinuxThreads是目前Linux平台上使用最為廣泛的線程庫,由Xavier Leroy ([email protected]) 負責開發完成,並已綁定在GLIBC中發行。它所實現的就是基於核心輕量級進程的"一對一"線程模型,一個線程實體對應一個核心輕量級進程,而線程之間的 管理在核外函數庫中實現。
1.線程描述數據結構及實現限制
LinuxThreads定義了一個struct _pthread_descr_struct數據結構來描述線程,並使用全局數組變數 __pthread_handles來描述和引用進程所轄線程。在__pthread_handles中的前兩項,LinuxThreads定義了兩個全 局的系統線程:__pthread_initial_thread和__pthread_manager_thread,並用 __pthread_main_thread表徵__pthread_manager_thread的父線程(初始為 __pthread_initial_thread)。
struct _pthread_descr_struct是一個雙環鏈表結構,__pthread_manager_thread所在的鏈表僅包括它 一個元素,實際上,__pthread_manager_thread是一個特殊線程,LinuxThreads僅使用了其中的errno、p_pid、 p_priority等三個域。而__pthread_main_thread所在的鏈則將進程中所有用戶線程串在了一起。經過一系列 pthread_create()之後形成的__pthread_handles數組將如下圖所示:
圖2 __pthread_handles數組結構
新創建的線程將首先在__pthread_handles數組中占據一項,然後通過數據結構中的鏈指針連入以__pthread_main_thread為首指針的鏈表中。這個鏈表的使用在介紹線程的創建和釋放的時候將提到。
LinuxThreads遵循POSIX1003.1c標准,其中對線程庫的實現進行了一些范圍限制,比如進程最大線程數,線程私有數據區大小等等。在 LinuxThreads的實現中,基本遵循這些限制,但也進行了一定的改動,改動的趨勢是放鬆或者說擴大這些限制,使編程更加方便。這些限定宏主要集中 在sysdeps/unix/sysv/linux/bits/local_lim.h(不同平台使用的文件位置不同)中,包括如下幾個:
每進程的私有數據key數,POSIX定義_POSIX_THREAD_KEYS_MAX為128,LinuxThreads使用 PTHREAD_KEYS_MAX,1024;私有數據釋放時允許執行的操作數,LinuxThreads與POSIX一致,定義 PTHREAD_DESTRUCTOR_ITERATIONS為4;每進程的線程數,POSIX定義為64,LinuxThreads增大到1024 (PTHREAD_THREADS_MAX);線程運行棧最小空間大小,POSIX未指定,LinuxThreads使用 PTHREAD_STACK_MIN,16384(位元組)。
2.管理線程
"一對一"模型的好處之一是線程的調度由核心完成了,而其他諸如線程取消、線程間的同步等工作,都是在核外線程庫中完成的。在LinuxThreads 中,專門為每一個進程構造了一個管理線程,負責處理線程相關的管理工作。當進程第一次調用pthread_create()創建一個線程的時候就會創建 (__clone())並啟動管理線程。
在一個進程空間內,管理線程與其他線程之間通過一對"管理管道(manager_pipe[2])"來通訊,該管道在創建管理線程之前創建,在成功啟動 了管理線程之後,管理管道的讀端和寫端分別賦給兩個全局變數__pthread_manager_reader和 __pthread_manager_request,之後,每個用戶線程都通過__pthread_manager_request向管理線程發請求, 但管理線程本身並沒有直接使用__pthread_manager_reader,管道的讀端(manager_pipe[0])是作為__clone ()的參數之一傳給管理線程的,管理線程的工作主要就是監聽管道讀端,並對從中取出的請求作出反應。
創建管理線程的流程如下所示:
(全局變數pthread_manager_request初值為-1)
圖3 創建管理線程的流程
初始化結束後,在__pthread_manager_thread中記錄了輕量級進程號以及核外分配和管理的線程id, 2*PTHREAD_THREADS_MAX+1這個數值不會與任何常規用戶線程id沖突。管理線程作為pthread_create()的調用者線程的 子線程運行,而pthread_create()所創建的那個用戶線程則是由管理線程來調用clone()創建,因此實際上是管理線程的子線程。(此處子 線程的概念應該當作子進程來理解。)
__pthread_manager()就是管理線程的主循環所在,在進行一系列初始化工作後,進入while(1)循環。在循環中,線程以2秒為 timeout查詢(__poll())管理管道的讀端。在處理請求前,檢查其父線程(也就是創建manager的主線程)是否已退出,如果已退出就退出 整個進程。如果有退出的子線程需要清理,則調用pthread_reap_children()清理。
然後才是讀取管道中的請求,根據請求類型執行相應操作(switch-case)。具體的請求處理,源碼中比較清楚,這里就不贅述了。
3.線程棧
在LinuxThreads中,管理線程的棧和用戶線程的棧是分離的,管理線程在進程堆中通過malloc()分配一個THREAD_MANAGER_STACK_SIZE位元組的區域作為自己的運行棧。
用戶線程的棧分配辦法隨著體系結構的不同而不同,主要根據兩個宏定義來區分,一個是NEED_SEPARATE_REGISTER_STACK,這個屬 性僅在IA64平台上使用;另一個是FLOATING_STACK宏,在i386等少數平台上使用,此時用戶線程棧由系統決定具體位置並提供保護。與此同 時,用戶還可以通過線程屬性結構來指定使用用戶自定義的棧。因篇幅所限,這里只能分析i386平台所使用的兩種棧組織方式:FLOATING_STACK 方式和用戶自定義方式。
在FLOATING_STACK方式下,LinuxThreads利用mmap()從內核空間中分配8MB空間(i386系統預設的最大棧空間大小,如 果有運行限制(rlimit),則按照運行限制設置),使用mprotect()設置其中第一頁為非訪問區。該8M空間的功能分配如下圖:
圖4 棧結構示意
低地址被保護的頁面用來監測棧溢出。
對於用戶指定的棧,在按照指針對界後,設置線程棧頂,並計算出棧底,不做保護,正確性由用戶自己保證。
不論哪種組織方式,線程描述結構總是位於棧頂緊鄰堆棧的位置。
4.線程id和進程id
每個LinuxThreads線程都同時具有線程id和進程id,其中進程id就是內核所維護的進程號,而線程id則由LinuxThreads分配和維護。
② LinuxC++如何編寫線程安全庫
LinuxC++編寫線程安全庫dll的方法:
1、動態庫只有一個導出函數。
這種情況下編寫函數時,只需要考慮不要有沖突的全局數據就可以了。這里的全局數據包括了在堆中分配的數據塊和靜態全局變數等。如果存在這樣的全局數據,那麼進程中的不同線程訪問這個函數就會造成沖突。
2、動態庫導出了多個函數,而且多個函數間存在數據傳遞。
一般DLL都導出多個函數,一個初始化,一個資源釋放,其他為核心功能函數。這些函數間極有可能發生數據傳遞。如果一個初始化函數是在線程A中調用的,而核心功能函數是在線程B中調用的,那麼線程A初始化函數的資源就無法對應線程B中的核心功能,此外還有核心功能函數間的數據傳遞,這樣的DLL就不是線程安全的,必然導致錯誤。
解決辦法是由用戶(即使用DLL的人)保證這些導出函數是在一個線程中調用。但這樣會很大程度上限制介面的設計和用戶的使用自由度。所以最好的方法是函數只管自己的線程安全,不同函數傳遞數據用動態TLS,線程局部存儲。
3、限制訪問DLL中某一函數的線程數目。
對於DLL中的某一個函數的訪問線程數目是有限制的,超過了限制其他線程就得等一定的時間,一定的時間過後如果還不能得到執行機會,那就返回超時。這樣的設計對用戶來說是友好的,而且很實用,有的商業程序確實是按照允許用戶訪問的通道數目來計價的。
對DLL中的函數做這樣的一個封裝,一般是簡單的待用Semaphore信號量,來解決。DLL初始化時調用CreateSemaphore函數對信號量進行初始化,其原型如下:
HANDLE CreateSemaphore(
LPSECURITY_ATTRIBUTES lpSemaphoreAttributes,
// pointer to security attributes
LONG lInitialCount, // initial count
LONG lMaximumCount, // maximum count
LPCTSTR lpName // pointer to semaphore-object name
);
對於信號量,它每WaitForSingleObject一次(當然是要進入),其狀態值(一個整數)就減1,使用完ReleaseSemaphore其狀態值就加1,當其狀態值為0時信號量就由有信號變為無信號。利用信號量的這一特性,我們在初始化時將信號量的初始值(第2個參數)設置為限制的線程訪問數目。在要限制訪問線程數目的函數內部,通過調用WaitForSingleOject獲取控制權,並指定一個等待時間(這個由配置文件指定),根據情況超時返回,使用完ReleaseSemaphore釋放對佔用,讓其他線程可以調用這個函數。
4、多進程情況下的多線程安全DLL。
LL是可以被多個進行載入並調用的。那就是說如果我們只對一個進程進行了限制,那麼在多進程調用的情況下,這樣的限制被輕易攻破。
我們都知道,Semaphore信號量屬於內核對象,也就是說其可以被多進程共享訪問,也就說,如果我們給一個Semaphore指定了一個名字,在另一個進程中,我們只要調用OpenSemaphore函數用同一名字打開信號量就可以訪問了。這樣問題就解決了?
現實情況是,多進程情況下,一般不是簡單的多進程共享一個Semaphore就可以了。多進程間需要互通很多信息。一般的解決辦法是,採用共享數據段。
#pragma data_seg("share")
int share_data;
#pragma data_seg()
#pragma comment(linker,"/SECTION:share, RWS")
通過pragam編譯器指令生成了一個名叫share的共享數據段,這樣對於變數share_data就可以多進程共享的了。如果要多進程間交換數據,只要在data_seg中添加數據定義即可。
③ Linux的線程庫為什麼是放在glibc中來實現
線程的實現有user level thread,kernel-level thread和兩者結合的方式。Linux的實現了kernel-level thread,共享進程地址空間的進程相當於線程。
PThread是user level thread,內核根本不了解其線程的存在,不會對其中的線程進行調度,更不會對其進行代碼實現。
④ Linux下的線程庫pthread庫中的pthread_create()函數創建兩個線程。
void * thread1() //線程1
{
//............. pthread_mutex_lock(&mut);
a += 1; //①
b = a; //②
pthread_mutex_unlock(&mut);
}
void * thread2() //線程2
{
//.............
pthread_mutex_lock(&mut);
a += 2;
pthread_mutex_unlock(&mut);
}
這樣就行呀, 加鎖後另一個要等待鎖釋放
⑤ C++在linux下怎麼多線程
#ifndefTHREAD_H_
#defineTHREAD_H_
#include<unistd.h>
#include<pthread.h>
classRunnable
{
public:
//運行實體
virtualvoidrun()=0;
};
//線程類
classThread:publicRunnable
{
private:
//線程初始化號
staticintthread_init_number;
//當前線程初始化序號
intcurrent_thread_init_number;
//線程體
Runnable*target;
//當前線程的線程ID
pthread_ttid;
//線程的狀態
intthread_status;
//線程屬性
pthread_attr_tattr;
//線程優先順序
sched_paramparam;
//獲取執行方法的指針
staticvoid*run0(void*pVoid);
//內部執行方法
void*run1();
//獲取線程序號
staticintget_next_thread_num();
public:
//線程的狀態-新建
staticconstintTHREAD_STATUS_NEW=0;
//線程的狀態-正在運行
staticconstintTHREAD_STATUS_RUNNING=1;
//線程的狀態-運行結束
staticconstintTHREAD_STATUS_EXIT=-1;
//構造函數
Thread();
//構造函數
Thread(Runnable*target);
//析構
~Thread();
//線程的運行體
voidrun();
//開始執行線程
boolstart();
//獲取線程狀態
intget_state();
//等待線程直至退出
voidjoin();
//等待線程退出或者超時
voidjoin(unsignedlongmillis_time);
//比較兩個線程時候相同,通過current_thread_init_number判斷
booloperator==(constThread*other_pthread);
//獲取this線程ID
pthread_tget_thread_id();
//獲取當前線程ID
staticpthread_tget_current_thread_id();
//當前線程是否和某個線程相等,通過tid判斷
staticboolis_equals(Thread*iTarget);
//設置線程的類型:綁定/非綁定
voidset_thread_scope(boolisSystem);
//獲取線程的類型:綁定/非綁定
boolget_thread_scope();
//設置線程的優先順序,1-99,其中99為實時,意外的為普通
voidset_thread_priority(intpriority);
//獲取線程的優先順序
intget_thread_priority();
};
intThread::thread_init_number=1;
inlineintThread::get_next_thread_num()
{
returnthread_init_number++;
}
void*Thread::run0(void*pVoid)
{
Thread*p=(Thread*)pVoid;
p->run1();
returnp;
}
void*Thread::run1()
{
thread_status=THREAD_STATUS_RUNNING;
tid=pthread_self();
run();
thread_status=THREAD_STATUS_EXIT;
tid=0;
pthread_exit(NULL);
}
voidThread::run()
{
if(target!=NULL)
{
(*target).run();
}
}
Thread::Thread()
{
tid=0;
thread_status=THREAD_STATUS_NEW;
current_thread_init_number=get_next_thread_num();
pthread_attr_init(&attr);
}
Thread::Thread(Runnable*iTarget)
{
target=iTarget;
tid=0;
thread_status=THREAD_STATUS_NEW;
current_thread_init_number=get_next_thread_num();
pthread_attr_init(&attr);
}
Thread::~Thread()
{
pthread_attr_destroy(&attr);
}
boolThread::start()
{
returnpthread_create(&tid,&attr,run0,this);
}
inlinepthread_tThread::get_current_thread_id()
{
returnpthread_self();
}
inlinepthread_tThread::get_thread_id()
{
returntid;
}
inlineintThread::get_state()
{
returnthread_status;
}
voidThread::join()
{
if(tid>0)
{
pthread_join(tid,NULL);
}
}
voidThread::join(unsignedlongmillis_time)
{
if(tid==0)
{
return;
}
if(millis_time==0)
{
join();
}
else
{
unsignedlongk=0;
while(thread_status!=THREAD_STATUS_EXIT&&k<=millis_time)
{
usleep(100);
k++;
}
}
}
boolThread::operator==(constThread*other_pthread)
{
if(other_pthread==NULL)
{
returnfalse;
}if(current_thread_init_number==(*other_pthread).current_thread_init_number)
{
returntrue;
}
returnfalse;
}
boolThread::is_equals(Thread*iTarget)
{
if(iTarget==NULL)
{
returnfalse;
}
returnpthread_self()==iTarget->tid;
}
voidThread::set_thread_scope(boolisSystem)
{
if(isSystem)
{
pthread_attr_setscope(&attr,PTHREAD_SCOPE_SYSTEM);
}
else
{
pthread_attr_setscope(&attr,PTHREAD_SCOPE_PROCESS);
}
}
voidThread::set_thread_priority(intpriority)
{
pthread_attr_getschedparam(&attr,¶m);
param.__sched_priority=priority;
pthread_attr_setschedparam(&attr,¶m);
}
intThread::get_thread_priority(){
pthread_attr_getschedparam(&attr,¶m);
returnparam.__sched_priority;
}
#endif/*THREAD_H_*/
⑥ linux系統多線程序為什麼鏈接線程庫
IBM有個傢伙做了個測試,發現切換線程context的時候,windows比linux快一倍多。進出最快的鎖(windows2k的 critical section和linux的pthread_mutex),windows比linux的要快五倍左右。當然這並不是說linux不好,而且在經過實際編程之後,綜合來...
⑦ linux 有什麼線程庫支持正真意義上的並發,可同時在多個cpu上運行多個線程
注意你說的是「多個CPU核心」還是「多個CPU」
如果你只一個CPU上的多個核心並發運行多個線程,那麼linux上用pthread線程庫最好了,gcc原生支持。
如果你指多個CPU,那麼假設你指的是開放式內存架構,多計算機集群,那麼用MPI訊息傳遞介面開發最好了。
⑧ 嚴重關註:在嵌入試LINUX系統該使用哪個線程庫
嚴重關註: 在嵌入試LINUX系統該使用哪個線程庫? 2006-12-13 21:30:02
分類: LINUX
在Redhat9.0下面, 創建一個進程。getpid();得到進程ID,然後在進程里用pthreadcreate幾個線程,在線程里調用getpid, pthreadgetselfid()的到進程ID和線程ID, 發現該線程的進程ID和它所屬的進程ID是一樣的, 線程ID卻不同。當是在小機里用gcc里的線程庫,得到的結果是線程ID和進程ID是不同的,即線程也當作進程來對待,而且多了幾個其它線程在運行,若線程當作進程來對待效率是比較低的。在網上查了下linux的線程庫,發現新內核中的LD用的是The Native POSIX Thread Library (NPTL), 而小機上用的是老的GCC里的線程庫。
如何在Redhat9上面用老的線程庫呢,在系統環境變數中加入LD_ASSUME_KERNEL=2.4.19就OK了
因為老版本內核中是不用NPTL庫的, 這樣LD連接程序的時候就不用NPTL, 在PC LINUX的
小機模擬器的模擬就和小機一樣不用NPTL了。
那麼到底是不是該把小機的線程庫換成NPTL呢, 看看來自http://www-128.ibm.com/developerworks/cn/linux/l-nptl/?ca=dwcn-newsletter-linux的測試吧:
Linux 線程庫性能測試與分析
楊沙洲國防科技大學計算機學院
2004 年 7 月 01 日
NPTL 成為 glibc "正選"線程庫後,它的性能如何受到很多人的關注。本文就針對NPTL 與 LinuxThreads 的性能比較,以及超線程、內核可搶占等特性對線程性能的影響進行了全面評測。
一、 前言
在 Linux 2.6.x 內核中,調度性能的改進是其中最引人注目的一部分[1]。NPTL(Native Posix Thread Library)[2]使用內核的新特性重寫了 Linux 的線程庫,取代歷史悠久而備受爭議的 LinuxThreads[3] 成為 glibc 的首選線程庫。
NPTL 的性能究竟如何?相對 LinuxThreads 又有哪些明顯的改進?在對NPTL進行全面分析之前,本文針對這兩種線程庫,以及內核中"內核可搶占"(Preemptible)和超線程(HyperThreading)[4]等特性進行了全面的性能評測,結果表明NPTL絕對值得廣大伺服器系統期待和使用。
回頁首
二、 Benchmark
1. 測試平台
進行本測試的硬體平台為浪潮NF420R伺服器[7],4個Hyperthreading-enabled Intel Xeon 2.2G處理器,4G內存。Linux選擇了Slackware 9.0發行版[8],所使用的內核源碼來自 www.kernel.org。
2. 針對測試:LMBench
lmbench是一個用於評價系統綜合性能的多平台開源benchmark[5],但其中沒有對線程的支持。其中有兩個測試進程性能的benchmark:lat_proc用於評測進程創建和終止的性能,lat_ctx用於評測進程切換的開銷。lmbench擁有良好的benchmark結構,只需要修改具體的Target程序(如lat_proc.c和lat_ctx.c),就可以借用lmbench的計時、統計系統得到我們關心的線程庫性能的數據。
基於lat_proc和lat_ctx的演算法,本文實現了lat_thread和lat_thread_ctx兩個benchmark。在lat_thread中,lat_proc被改造成使用線程,用pthread_create()替代了fork(),用pthread_join()替代wait();在lat_thread_ctx中,沿用lat_ctx的評測演算法(見lat_ctx手冊頁),將創建進程的過程改寫為創建線程,仍然使用管道進行通信和同步。
lat_thread null
null參數表示線程不進行任何實際操作,創建後即刻返回。
lat_thread_ctx -s #threads
size參數與lat_ctx定義相同,可表示線程的大小(實際編程時為分配K數據;#threads參數為線程數,即參與令牌傳遞的線程總數,相當於程序負載情況。
3. 綜合測試:Volanomark
volanomark是一個純java的benchmark,專門用於測試系統調度器和線程環境的綜合性能[6],它建立一個模擬Client/Server方式的Java聊天室,通過獲取每秒平均發送的消息數來評測宿主機綜合性能(數值越大性能越好)。Volanomark測試與Java虛擬機平台相關,本文使用Sun Java SDK 1.4.2作為測試用Java平台,Volanomark版本2.5.0.9。
回頁首
三、 測試結果
測試計劃中將內核分為2.4.26、2.6.6/支持內核搶占和2.6.6/不支持內核搶佔三類;通過配置內核以及NF420R的BIOS實現三類SMP規模:單處理機(UP)、4CPU的SMP(SMP4)和打開超線程支持的虛擬8CPU SMP(SMP8*)。內核配置和SMP規模的每一種組合都針對LinuxThreads和NPTL使用lat_thread、lat_thread_ctx和volanomark獲取一組數據。由於NPTL無法在2.4.x內核上使用,該項數據空缺。
回頁首
四、 結果分析
1. LinuxThreads vs NPTL:線程創建/銷毀開銷
使用2.6.6/preemptible內核配置下UP和SMP4的測試數據獲得下圖:
圖1
在線程創建/銷毀開銷方面,NPTL的改進相當明顯(降低約600%)。實際上,NPTL不再像LinuxThreads那樣需要使用用戶級的管理線程來維護線程的創建和銷毀[9],因此,很容易理解它在這方面的開銷能夠大幅度降低。
同時,由圖可見,單CPU下創建線程總是比多CPU下迅速。
2. LinuxThreads vs NPTL:線程切換開銷
同樣使用2.6.6/preemptible內核配置下UP和SMP4的數據:
圖2
隨著lat_thread_ctx的參與線程增多,不管是哪個線程庫,單處理機條件下的線程切換開銷都陡峭上升,而SMP條件下則上升比較平緩。在這方面,LinuxThreads和NPTL表現基本相同。
3. 內核影響
圖3
圖4
圖5
圖6
從上面四張圖中我們可以得出兩點結論:
"內核可搶占"是Linux對實時應用提供更好支持的有力保障,但對線程性能影響很小,甚至有一點損失,畢竟搶占鎖的開銷不可忽略;
升級內核並不會對LinuxThreads線程庫性能帶來多少變化,因此,對於伺服器系統而言,不能指望僅僅編譯使用新內核就能提高性能。
圖7
圖8
從圖3、圖4我們已經知道,打開超線程支持對線程創建/銷毀性能幾乎沒有影響,而這兩張圖表也進一步說明,超線程技術對於線程切換開銷也沒有明顯的影響。超線程技術是CPU內部的優化技術,和真正的雙CPU完全不同。大量研究表明,如果沒有內核與用戶應用相結合的專門優化措施,超線程並不會帶來很大的性能變化。除非是高負載綜合伺服器系統(例如繁忙的資料庫系統),購買超線支持的CPU並不能帶來多少好處。
4. 綜合性能
圖9
圖9
前面幾節分析讓我們了解了線程庫性能改進的細節,通過volanomark測試,我們可以近似得到在綜合應用環境下,特別是網路服務需求中線程庫以及內核對系統整體性能的影響程度。
圖9綜合了不同內核、不同處理機數條件下,兩種線程庫的volanomark結果。從圖中可以觀察到以下三點:
NPTL能極大提高SMP環境下伺服器系統的整體性能(超過65%),相對而言,對單處理機系統影響較小(10%左右);
2.6內核的搶占特性對系統性能影響很小(不超過±1%),某些情況下甚至有所下降;
超線程技術在LinuxThreads中的影響是負面的,在NPTL中是正面的,但影響幅度都很小(5%-6%)。
以上結論中前兩點與LMBench針對性測試結果完全吻合,第三點的偏差實際上反映了超線程技術對於綜合伺服器環境還是有一定加速的。
⑨ 為什麼linux的線程庫是第三方庫,像posix線程庫里的線程和linux內核線程一樣嗎
嚴格來說,在Linux的體系中,用戶空間是沒有Thread這個概念的,Thread的相關實現是gcc等提供的模擬thread, gcc是使用了clone這個系統調用,利用linux的輕量級進程實現了類似thread的庫。這些內容你可以在《unix環境高級編程》這本書裡面看到很清晰完整的講解。
至於Linux為何不在用戶空間實現thread,這只是一種選擇問題,讀一下《操作系統-內核與設計原理》這本書應該有所幫助。
⑩ LINUX中GCC支持C++線程嗎如果支持那麼C++的線程庫是什麼
#include <thread> // GCC 4.7
#include <thread> // VC++11.0
#include <thread>
#include <iostream>
int main() {
std::thread t1([]() {
std::cout << "Hello," << std::endl;
});
std::thread t2([]() {
std::cout << "Fuck" << std::endl;
});
t1.join();
t2.join();
system("pause");
}