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