线程栈学习总结
線程棧和進(jìn)程棧 區(qū)別
http://blog.csdn.net/kickxxx/article/details/9278193要搞清線程棧和進(jìn)程棧的區(qū)別,首先要弄清線程和進(jìn)程之間的關(guān)系。
線程和進(jìn)程有很多類似的地方,人們習(xí)慣上把線程稱為輕量級進(jìn)程,這個所謂的輕量級是指線程并不擁有自己的系統(tǒng)資源,線程依附于創(chuàng)建自己的進(jìn)程。
我們可以從l兩個個方面來理解線程的輕量級
1. 調(diào)度
由于進(jìn)程之間的線程共享同一個進(jìn)程地址空間,因此在進(jìn)程的線程之間做進(jìn)程切換,并不會引起進(jìn)程地址空間的切換,從而避免了昂貴的進(jìn)程切換。當(dāng)然不同進(jìn)程組之間是需要進(jìn)程切換的
2. 擁有資源
進(jìn)程是操作系統(tǒng)中擁有資源的獨立單位,在創(chuàng)建和撤銷進(jìn)程時,操作系統(tǒng)都會為進(jìn)程分配和回收資源,資源包括地址空間,文件,IO,頁表等。但是由于線程是依附與創(chuàng)建進(jìn)程的,線程的代碼段,數(shù)據(jù)段,打開文件,IO資源,地址空間,頁表等都是和進(jìn)程的所有線程共享的。
從上面我們看出線程并沒有獨立的地址空間,這就意味著隸屬同一進(jìn)程的所有線程棧,都在所屬進(jìn)程的地址空間中,他們的棧地址不同,但是如果操作棧時發(fā)生越界,是有可能破壞其他線程的棧空間的。
而進(jìn)程實際上可以看作是主線程,它的棧和其它線程棧沒有區(qū)別。
單線程只有一個棧,多線程則為每個線程都分配一個棧,并且這些棧的地址不同,可以通過如下方法驗證這個結(jié)論
1. pslist輸出系統(tǒng)進(jìn)程以及他們的線程,在我的機器上得到如下結(jié)果
1889 gnome-session 1918 1926 1940 1969 1957 2282 2283 1971 1972 1973 1975 1998 2003 2010 2669 2691 2710 2776 2871 ?
1889是主線程,后面是這個進(jìn)程創(chuàng)建的線程
2. 對每一個線程ID執(zhí)行,cat /proc/threadID/maps
可以看到?jīng)]個線程的stack地址范圍各不相同,這也從側(cè)面驗證了每個線程的棧地址在同一進(jìn)程地址空間的不同地址范圍內(nèi)。
========
線程堆棧
http://blog.csdn.net/nokianasty/article/details/7600321線程堆棧!
? ? ? ?一個線程的開銷包括:
? ? ? 內(nèi)核模式下的開銷(內(nèi)核堆棧,對象管理所需內(nèi)存)
? ? ? 用戶模式下的開銷(線程局部存儲、線程環(huán)境塊、堆棧、CRT、MFC、COM等等等等)
? ? ? 通常,線程數(shù)目的瓶頸在于線程自己的堆棧。Visual C++編譯器默認(rèn)設(shè)置是每個線程的堆棧大小是1MB。當(dāng)然,如果你在創(chuàng)建線程時指定較小的堆棧大小,你應(yīng)該可以創(chuàng)建較多的線程。
? ? ? 但是創(chuàng)建大量線程不是一個好的設(shè)計。每個線程創(chuàng)建和銷毀的時候,Windows會調(diào)用已經(jīng)加載的動態(tài)鏈接庫的DLLMain,傳遞DLL_THREAD_ATTACH和DLL_THREAD_DETACH作為參數(shù),除非動態(tài)庫使用DisableThreadLibraryCalls禁用了這個通知。在創(chuàng)建大量線程的時候,這個開銷是很大的。對于你這樣的用后即棄的線程,你應(yīng)該使用線程池。一個線程池示例可以在微軟知識庫找到。
? ? ?參數(shù)和局部變量的函數(shù)都存儲在線程的堆棧。 如果聲明局部變量具有大型值, 堆棧快速耗盡。 例如, 在以下代碼示例函數(shù)要求堆棧來存儲數(shù)組 1,200,000 個字節(jié)。
void func(void)
? ?{
? ? ?int i[300000];
? ? ?// Use 300,000 integers multiplied by 4 bytes per integer to store the array.
? ? ?return;
? ?}
要避免使用堆棧, 使用動態(tài)分配內(nèi)存。 例如, 在以下代碼示例函數(shù)動態(tài)分配內(nèi)存。
void func(void)
? ?{
? ? ?int *i
? ? ?i = new int[400000];
? ? ?// More code goes here.
? ? ?return;
? ?}
? ?在此代碼示例, 內(nèi)存存儲在堆棧代替。 因此, 函數(shù)不需要堆棧來存儲數(shù)組 1,200,000 個字節(jié)。
由此也解開了為什么我在一個進(jìn)程中創(chuàng)建大于2000個線程時導(dǎo)致程序異常退出,因為每個線程的默認(rèn)堆
棧是1MB,2000*1MB = 2GB,my God!
可以在創(chuàng)建線程時指定線程堆棧大小,這樣就能創(chuàng)建更多的線程了!但是前面提到,大量的創(chuàng)建線程并銷
毀,會帶來大量的系統(tǒng)開銷,所以盡量避免創(chuàng)建大量線程,推薦使用線程池,但是偶現(xiàn)在還不會用,快去查查!
========
Linux 進(jìn)程棧和線程棧的區(qū)別
http://www.cnblogs.com/jingzhishen/p/4433437.html?
總結(jié):線程棧的空間開辟在所屬進(jìn)程的堆區(qū),線程與其所屬的進(jìn)程共享進(jìn)程的用戶空間,所以線程棧之間可以互訪。線程棧的起始地址和大小存放在pthread_attr_t 中,棧的大小并不是用來判斷棧是否越界,而是用來初始化避免棧溢出的緩沖區(qū)的大小(或者說安全間隙的大小)
?
進(jìn)程內(nèi)核棧、用戶棧
?
1.進(jìn)程的堆棧
? ? ?內(nèi)核在創(chuàng)建進(jìn)程的時候,在創(chuàng)建task_struct的同事,會為進(jìn)程創(chuàng)建相應(yīng)的堆棧。每個進(jìn)程會有兩個棧,一個用戶棧,存在于用戶空間,一個內(nèi)核棧,存 在于內(nèi)核空間。當(dāng)進(jìn)程在用戶空間運行時,cpu堆棧指針寄存器里面的內(nèi)容是用戶堆棧地址,使用用戶棧;當(dāng)進(jìn)程在內(nèi)核空間時,cpu堆棧指針寄存器里面的內(nèi) 容是內(nèi)核棧空間地址,使用內(nèi)核棧。
2.進(jìn)程用戶棧和內(nèi)核棧的切換
? ? 當(dāng)進(jìn)程因為中斷或者系統(tǒng)調(diào)用而陷入內(nèi)核態(tài)之行時,進(jìn)程所使用的堆棧也要從用戶棧轉(zhuǎn)到內(nèi)核棧。
? ? 進(jìn)程陷入內(nèi)核態(tài)后,先把用戶態(tài)堆棧的地址保存在內(nèi)核棧之中,然后設(shè)置堆棧指針寄存器的內(nèi)容為內(nèi)核棧的地址,這樣就完成了用戶棧向內(nèi)核棧的轉(zhuǎn)換;當(dāng)進(jìn)程從內(nèi) 核態(tài)恢復(fù)到用戶態(tài)之行時,在內(nèi)核態(tài)之行的最后將保存在內(nèi)核棧里面的用戶棧的地址恢復(fù)到堆棧指針寄存器即可。這樣就實現(xiàn)了內(nèi)核棧和用戶棧的互轉(zhuǎn)。
? ? 那么,我們知道從內(nèi)核轉(zhuǎn)到用戶態(tài)時用戶棧的地址是在陷入內(nèi)核的時候保存在內(nèi)核棧里面的,但是在陷入內(nèi)核的時候,我們是如何知道內(nèi)核棧的地址的呢?
? ? 關(guān)鍵在進(jìn)程從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài)的時候,進(jìn)程的內(nèi)核棧總是空的。這是因為,當(dāng)進(jìn)程在用戶態(tài)運行時,使用的是用戶棧,當(dāng)進(jìn)程陷入到內(nèi)核態(tài)時,內(nèi) 核棧保存進(jìn)程在內(nèi)核態(tài)運行的相關(guān)信心,但是一旦進(jìn)程返回到用戶態(tài)后,內(nèi)核棧中保存的信息無效,會全部恢復(fù),因此每次進(jìn)程從用戶態(tài)陷入內(nèi)核的時候得到的內(nèi)核 棧都是空的(為什么?)。所以在進(jìn)程陷入內(nèi)核的時候,直接把內(nèi)核棧的棧頂?shù)刂方o堆棧指針寄存器就可以了。
3.內(nèi)核棧的實現(xiàn)
? ? ? ? 內(nèi)核棧在kernel-2.4和kernel-2.6里面的實現(xiàn)方式是不一樣的。
?在kernel-2.4內(nèi)核里面,內(nèi)核棧的實現(xiàn)是:
?Union task_union {
? ? ? ? ? ? ? ? ? ?Struct task_struct task;
? ? ? ? ? ? ? ? ? ?Unsigned long stack[INIT_STACK_SIZE/sizeof(long)];
?};
?其中,INIT_STACK_SIZE的大小只能是8K。
? ? ?內(nèi)核為每個進(jìn)程分配task_struct結(jié)構(gòu)體的時候,實際上分配兩個連續(xù)的物理頁面,底部用作task_struct結(jié)構(gòu)體,結(jié)構(gòu)上面的用作堆棧。使用current()宏能夠訪問當(dāng)前正在運行的進(jìn)程描述符。
?注意:這個時候task_struct結(jié)構(gòu)是在內(nèi)核棧里面的,內(nèi)核棧的實際能用大小大概有7K。
內(nèi)核棧在kernel-2.6里面的實現(xiàn)是(kernel-2.6.32):
?Union thread_union {
? ? ? ? ? ? ? ? ? ?Struct thread_info thread_info;
? ? ? ? ? ? ? ? ? ?Unsigned long stack[THREAD_SIZE/sizeof(long)];
?};
?其中THREAD_SIZE的大小可以是4K,也可以是8K,thread_info占52bytes。
? ? ?當(dāng)內(nèi)核棧為8K時,Thread_info在這塊內(nèi)存的起始地址,內(nèi)核棧從堆棧末端向下增長。所以此時,kernel-2.6中的current宏是需要 更改的。要通過thread_info結(jié)構(gòu)體中的task_struct域來獲得于thread_info相關(guān)聯(lián)的task。更詳細(xì)的參考相應(yīng)的 current宏的實現(xiàn)。
?struct thread_info {
? ? ? ? ? ? ? ? ? ?struct task_struct *task;
? ? ? ? ? ? ? ? ? ?struct exec_domain *exec_domain;
? ? ? ? ? ? ? ? ? ?__u32 flags;
? ? ? ? __u32 status;
? ? ? ? ? ? ? ? ? ?__u32 cpu;
? ? ? ? ? ? ? ? ? ?… ?..
?};
?注意:此時的task_struct結(jié)構(gòu)體已經(jīng)不在內(nèi)核棧空間里面了。
========
多線程 - 你知道線程棧嗎
http://www.cnblogs.com/snake-hand/p/3148191.html問題
1. local 變量的壓棧和出棧過程
void func1(){
? ? int a = 0;
? ? int b = 0;
}
系統(tǒng)中有一個棧頂指針,每次分配和回收local 變量時,其實就是移動棧指針。
2. static local變量的分配風(fēng)險
void func2(){
? ? static int a = 0;
}
這個變量a可能會被分配多次,因為如果func2可能同時被多個線程調(diào)用,也就是函數(shù)在分配內(nèi)存時是可能出現(xiàn)線程切換的。
問題:
如
void func3(){
int a;
int b;
}
void func4(){
int c;
int d;
}
假設(shè),func3和func4分別被兩個線程調(diào)用,并且func3先于func4執(zhí)行,并且4個變量壓棧的順序分別是a、b、c、d。按照上面第1個說明,這個時候棧頂指針指向d。
如果,這個時候func3先執(zhí)行完,那么這個時候,系統(tǒng)要回收b和a,但是b并不在棧頂,所以,無法移動棧頂指針,所以,b和a無法回收。最復(fù)雜的情況可能如下,壓棧的順序是a、c、d、b,這個時候b可以正常回收。當(dāng)要回收a時,會不會誤把d當(dāng)作a給回收了?應(yīng)該怎么解釋這個問題呢。
顯然,事實上并非上面所述,因為線程里有一個很重要的屬性stacksize,它讓我們隱約感覺到,線程是擁有私有的棧空間的,如果這樣,abcd的壓棧出棧就不會有問題了,因為他們并不保存在一起。
pthread線程棧
?
#include <stdio.h>
#include <pthread.h>
void* thread1(void* a)
{
char m[8388608];
printf("thread1\n");
}
int main(){
pthread_t pthread_id;
pthread_attr_t thread_attr;
int status;
status = pthread_attr_init(&thread_attr);
if(status != 0)
printf("init error\n");
size_t stacksize = 100;
status = pthread_attr_getstacksize(&thread_attr, &stacksize);
printf("stacksize(%d)\n", stacksize);
//printf("size(%d)\n", sizeof(int));
status = pthread_create(&pthread_id, NULL, thread1, NULL);
while(1)
{}
return 0;
}
運行結(jié)果:
stacksize(8388608)
段錯誤
分析
pthread_attr_getstacksize可以獲得線程的私有棧的大小,我這個機器是8388608字節(jié),為8M,也就是私有棧最大是8M,所以,創(chuàng)建的一個線程函數(shù)里有個局部數(shù)組長度為8M,顯示段錯誤(雖然數(shù)組大小和私有棧一樣大,但是私有棧除了分配局部變量外,還要保存一些管理信息,所以肯定要小于8M),如果將數(shù)組長度減小一定的值,就可以看到thread1函數(shù)的打印信息。
pthread線程內(nèi)存布局
我們從圖上可以看出,兩個線程之間的棧是獨立的,其他是共享的,所以,在操作共享區(qū)域的時候才有可能出現(xiàn)同步需要,操作棧不需要同步。
最后我們知道,pthread也提供了私有堆機制,關(guān)于私有堆機制在以后說明。
========
總結(jié)
- 上一篇: C#字符串截取学习总结
- 下一篇: C# System.Runtime.In