【概念上】
一 進程是具有一定獨立功能的程式關於某個資料集合上的一次運行活動,是系統進行資源分派和調度的一個獨立單位;
二 線程是進程的一個實體,是CPU調度和指派的基本單位,它是比進程更小的能獨立啟動並執行基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程式計數器,一組寄存器和棧),一個線程可以建立和撤銷另一個線程;
【進程與線程區別與聯絡】
(1) 劃分尺度:線程更小,所以多線程程式並發性更高;
(2) 資源分派:進程是資源分派的基本單位,同一進程內多個線程共用其資源;
(3) 地址空間:進程擁有獨立的地址空間,同一進程內多個線程共用其資源;
(4) 處理器調度:線程是處理器調度的基本單位;
(5) 執行:每個線程都有一個程式啟動並執行入口,順序執行序列和程式的出口,但線程不能單獨執行,必須組成進程,一個進程至少有一個主線程。簡而言之,一個程式至少有一個進程,一個進程至少有一個線程.
【linux上的進程與線程】
一般來說,線程是windows上的概念,windows區分進程和線程。而在linux(老版本)上,統一叫進程,進程是完成某項任務所需資源的集合,同時也是linux基本的執行單元。
Linux線程是通過進程來實現。Linux kernel為進程建立提供一個clone()系統調用,clone的參數包括如
CLONE_VM, CLONE_FILES, CLONE_SIGHAND 等。通過clone()的參數,新建立的進程,也稱為LWP(Lightweight
process)與父進程共用記憶體空間,檔案控制代碼,訊號處理等,從而達到建立線程相同的目的。
Linux 2.6的線程庫叫NPTL(Native POSIX Thread Library)。POSIX
thread(pthread)是一個編程規範,通過此規範開發的多線程程式具有良好的跨平台特性。儘管是基於進程的實現,但新版的NPTL建立線程的效率非常高。一些測試顯示,基於NPTL的核心建立10萬個線程只需要2秒,而沒有NPTL支援的核心則需要長達15分鐘。
在Linux 2.6之前,Linux kernel並沒有真正的thread支援,一些thread
library都是在clone()基礎上的一些基於user space的封裝,因此通常在訊號處理、進程調度(每個進程需要一個額外的調度線程)及多線程之間同步共用資源等方面存在一定問題。為瞭解決這些問題,當年IBM曾經開發一套NGPT(NextGeneration
POSIX Threads), 效率比 LinuxThreads有明顯改進,但由於NPTL的推出,NGPT也完成了相關的曆史使命並停止了開發。
NPTL的實現是在kernel增加了futex(fast
userspace mutex)支援用於處理線程之間的sleep與wake。futex是一種高效的對共用資源互斥訪問的演算法。kernel在裡面起仲裁作用,但通常都由進程自行完成。
NPTL是一個1×1的執行緒模式,即一個線程對於一個作業系統的調度進程,優點是非常簡單。而其他一些作業系統比如Solaris則是MxN的,M對應建立的線程數,N對應作業系統可以啟動並執行實體。(N<M),優點是線程切換快,但實現稍複雜。
【執行環境】
每個執行環境都有自己的狀態,包括CPU狀態,記憶體映射狀態,許可權狀態(uid,pid)和各種各樣的通訊狀態(開啟的檔案描述符,管道,訊號處理常式等)。
而線程和進程的不同在於線程這種執行環境之間共用了很多狀態,至於共用哪些狀態不取決於書上說了什麼(書上只說了一點點),而是取決於建立這個執行環境的函數提供了哪些選擇。
在linux下,線程通過clone這個函數建立,而這個函數和fork一樣,其實是用來建立進程的,但是clone在建立進程的時候,會指定調用這個函數的進程和新建立的進程之間共用的內容,比如說pid,程式碼片段,檔案描述符等等,所以看上去好像一個進程裡面有很多股進程在並行運行,這些並行啟動並執行進程後來有了個新名字:線程。所以大家不必太在意他們叫什麼名字,就好比給皇上吃的胡蘿蔔叫“宮廷胡蘿蔔”一樣,換個名字都是唬人的,只要搞清楚實質就好了。
而我們建立新線程(進程)也可以不和老進程共用那些內容,比如不共用檔案描述符,這樣線程(子進程)可以開啟的檔案數就不會受到父進程的限制了(通常一個進程可以開啟的檔案數是有限制的,可以使用ulimit -n查看,一般是1024);再比如不共用pid,這樣新線程也有自己的pid(進程id),從這也可以看出線程其實就是進程。
【記憶體中】
概念縷清楚了,後面的內容還是回到預設寫法,大家看到線程、進程,心裡清楚其實就是COE就好了。
多線程程式和普通程式在記憶體中的的不同主要表現在棧的不同,每個線程一個棧,所以線程的局部變數不會受其他線程影響。而.text, .data, .bss, 等部分各個線程是共用的,所以線程間通訊非常簡單,直接在這些共用的記憶體中存取就可以了,所謂禍福相依,線程內操作這些共用記憶體中的資料的時候,也需要非常小心,要確保只有當前線程在操作全域變數(通過鎖或者其他一些操作)。
再就沒什麼好說的了,感覺上一篇部落格已經講的比較清楚了,最後就是一個樣本程式了:
【程式執行個體】
#include <stdio.h>#include <unistd.h>#include <stdlib.h>#include <pthread.h>void *thread_foo_func(void *);void *thread_bar_func(void *);int global = 4;int main(){ int local = 8; int foo, bar; pthread_t fthread, bthread; foo = pthread_create(&fthread, NULL, thread_foo_func, (void *)&local); bar = pthread_create(&bthread, NULL, thread_bar_func, (void *)&local); if (foo != 0 || bar != 0){ printf("thread creation failed.\n"); return -1; } foo = pthread_join(fthread, NULL); bar = pthread_join(bthread, NULL); if (foo != 0 || bar != 0){ printf("thread join failed.\n"); return -2; } char i; scanf("%c", &i); return 0;}void *thread_foo_func(void *arg){ int foo_local = 16; printf("address of global %d: %x\n", global, &global); printf("address of main local %d: %x\n", *(int *)arg, arg); printf("address of foo local: %x\n", &foo_local); char i; scanf("%c", &i);}void *thread_bar_func(void *arg){ int bar_local = 32; printf("address of global %d: %x\n", global, &global); printf("address of main local %d: %x\n", *(int *)arg, arg); printf("address of bar local: %x\n", &bar_local); char i; scanf("%c", &i);}
運行結果如下:
~/interview$ ./a.out address of global 4: 601048address of main local 8: 4beec428address of foo local: 639fee88address of global 4: 601048address of main local 8: 4beec428address of bar local: 631fde88
~/interview$ cat /proc/6470/maps 00400000-00401000 r-xp 00000000 08:05 396514 /home/zhangshijun/interview/a.out00600000-00601000 r--p 00000000 08:05 396514 /home/zhangshijun/interview/a.out00601000-00602000 rw-p 00001000 08:05 396514 /home/zhangshijun/interview/a.out01597000-015b8000 rw-p 00000000 00:00 0 [heap]7ffc629fe000-7ffc629ff000 ---p 00000000 00:00 0 7ffc629ff000-7ffc631ff000 rw-p 00000000 00:00 0 7ffc631ff000-7ffc63200000 ---p 00000000 00:00 0 7ffc63200000-7ffc63a00000 rw-p 00000000 00:00 0 7ffc63a00000-7ffc63b7a000 r-xp 00000000 08:07 21757969 /lib/libc-2.11.1.so7ffc63b7a000-7ffc63d79000 ---p 0017a000 08:07 21757969 /lib/libc-2.11.1.so7ffc63d79000-7ffc63d7d000 r--p 00179000 08:07 21757969 /lib/libc-2.11.1.so7ffc63d7d000-7ffc63d7e000 rw-p 0017d000 08:07 21757969 /lib/libc-2.11.1.so7ffc63d7e000-7ffc63d83000 rw-p 00000000 00:00 0 7ffc63d83000-7ffc63d99000 r-xp 00000000 08:07 21758007 /lib/libgcc_s.so.17ffc63d99000-7ffc63f98000 ---p 00016000 08:07 21758007 /lib/libgcc_s.so.17ffc63f98000-7ffc63f99000 r--p 00015000 08:07 21758007 /lib/libgcc_s.so.17ffc63f99000-7ffc63f9a000 rw-p 00016000 08:07 21758007 /lib/libgcc_s.so.17ffc63f9a000-7ffc6401c000 r-xp 00000000 08:07 21757973 /lib/libm-2.11.1.so7ffc6401c000-7ffc6421b000 ---p 00082000 08:07 21757973 /lib/libm-2.11.1.so7ffc6421b000-7ffc6421c000 r--p 00081000 08:07 21757973 /lib/libm-2.11.1.so7ffc6421c000-7ffc6421d000 rw-p 00082000 08:07 21757973 /lib/libm-2.11.1.so7ffc6421d000-7ffc64313000 r-xp 00000000 08:07 111412749 /usr/lib/libstdc++.so.6.0.137ffc64313000-7ffc64513000 ---p 000f6000 08:07 111412749 /usr/lib/libstdc++.so.6.0.137ffc64513000-7ffc6451a000 r--p 000f6000 08:07 111412749 /usr/lib/libstdc++.so.6.0.137ffc6451a000-7ffc6451c000 rw-p 000fd000 08:07 111412749 /usr/lib/libstdc++.so.6.0.137ffc6451c000-7ffc64531000 rw-p 00000000 00:00 0 7ffc64531000-7ffc64549000 r-xp 00000000 08:07 21757983 /lib/libpthread-2.11.1.so7ffc64549000-7ffc64748000 ---p 00018000 08:07 21757983 /lib/libpthread-2.11.1.so7ffc64748000-7ffc64749000 r--p 00017000 08:07 21757983 /lib/libpthread-2.11.1.so7ffc64749000-7ffc6474a000 rw-p 00018000 08:07 21757983 /lib/libpthread-2.11.1.so7ffc6474a000-7ffc6474e000 rw-p 00000000 00:00 0 7ffc6474e000-7ffc6476e000 r-xp 00000000 08:07 21757963 /lib/ld-2.11.1.so7ffc64960000-7ffc64964000 rw-p 00000000 00:00 0 7ffc64969000-7ffc6496d000 rw-p 00000000 00:00 0 7ffc6496d000-7ffc6496e000 r--p 0001f000 08:07 21757963 /lib/ld-2.11.1.so7ffc6496e000-7ffc6496f000 rw-p 00020000 08:07 21757963 /lib/ld-2.11.1.so7ffc6496f000-7ffc64970000 rw-p 00000000 00:00 0 7fff4bed8000-7fff4beed000 rw-p 00000000 00:00 0 [stack]7fff4bfff000-7fff4c000000 r-xp 00000000 00:00 0 [vdso]ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
關鍵看看幾個局部變數的記憶體的範圍,main函數的局部變數地址是:4beec428,在main函數棧的範圍內:7fff4bed8000-7fff4beed000
foo的局部變數在foo的棧裡面,記憶體範圍是:7ffc63200000-7ffc63a00000。
bar的局部變數在bar的棧裡面,記憶體範圍是:7ffc629ff000-7ffc631ff000。
參考文獻:
http://blog.csdn.net/high_high/article/details/7204097
http://www.kernel.org/doc/man-pages/index.html
Implementing a Thread Library on Linux
linus本人發的一個文章,值得一看,猛擊進入
Professional Linux kernel architecture. Wolfgang Mauerer. 2008