UNIX再学习 -- 线程
生活随笔
收集整理的這篇文章主要介紹了
UNIX再学习 -- 线程
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
終于要講到線程部分,線程和進程讓人夠頭痛的內容。
一、線程概念
老樣子,我們還是按我們講進程時的方式說起,參看:UNIX再學習 -- 進程環境 首先需要了解下,什么是線程。Linux 下的線程,可能我們比較陌生,但是我們一直在玩 Windows 系統。應用程序文件、任務管理器,這些東西應該是很溜的。比如:查看詳細信息,最上一列,右擊選擇列;找到線程打對勾。如下圖,就可看到線程數了。
通過上圖我們可以看到?一個進程中有多個線程。但是到底什么是線程呢? 參看:進程與線程的一個簡單解釋??大神舉得例子很有意思。 進程好似車間,線程好似車間工人;任務是有很多工人協同完成;車間內的空間設施工人都是共享的;有些房間比如廁所一次只能容納一人,進入需加鎖(互斥鎖)。
進程,是資源分配單位。線程,是CPU調度基本單位。 線程就是程序的執行路線,即進程內部的控制序列,或者說是進程的子任務。 一個進程可以同時擁有多個線程,即同時被系統調度的多條執行路線,但至少要有一個主線程。
一個進程的所有線程都共享進程的代碼區、數據區、堆區(注意沒有棧區)、環境變量和命令行參數、文件描述符、信號處理函數、當前目錄、用戶 ID 和組 ID 等。 一個進程的每個線程都擁有獨立的 ID、寄存器值、棧內存、調度策略和優先級、信號掩碼、errno變量以及線程私有數據等。 也可以說線程是包含在進程中的一種實體。它有自己的運行線索,可完成特定任務。可與其他線程共享進程中的共享變量及部分環境。可通過相互之間協同來完成進程所要完成的任務。 之前有轉載一篇文章,可當擴展來看,參看:進程與線程及其區別
1.進程和線程的區別
什么是進程(Process):普通的解釋就是,進程是程序的一次執行,而什么是線程(Thread),線程可以理解為進程中的執行的一段程序片段。在一個多任務環境中下面的概念可以幫助我們理解兩者間的差別:進程間是獨立的,這表現在內存空間,上下文環境;線程運行在進程空間內。一般來講(不使用特殊技術)進程是無法突破進程邊界存取其他進程內的存儲空間;而線程由于處于進程空間內,所以同一進程所產生的線程共享同一內存空間。 同一進程中的兩段代碼不能夠同時執行,除非引入線程。線程是屬于進程的,當進程退出時該進程所產生的線程都會被強制退出并清除。線程占用的資源要少于進程所占用的資源。 進程和線程都可以有優先級。在線程系統中進程也是一個線程。可以將進程理解為一個程序的第一個線程。
線程是指進程內的一個執行單元,也是進程內的可調度實體.與進程的區別:
(1)地址空間:進程內的一個執行單元;進程至少有一個線程;它們共享進程的
地址空間;而進程有自己獨立的地址空間;
(2)進程是資源分配和擁有的單位,同一個進程內的線程共享進程的資源
(3)線程是處理器調度的基本單位,但進程不是.
(4)二者均可并發執行.
1.進程和線程的差別。
答:線程是指進程內的一個執行單元,也是進程內的可調度實體.
與進程的區別:
(1)調度:線程作為調度和分配的基本單位,進程作為擁有資源的基本單位
(2)并發性:不僅進程之間可以并發執行,同一個進程的多個線程之間也可并發執行
(3)擁有資源:進程是擁有資源的一個獨立單位,線程不擁有系統資源,但可以訪問隸屬于
進程的資源.
(4)系統開銷:在創建或撤消進程時,由于系統都要為之分配和回收資源,導致系統的開銷
明顯大于創建或撤消線程時的開銷。
二、POSIX 線程
早期 UNIX 廠商各自提供私有的線程庫版本,無論是接口還是實現,差異都非常大,代碼移植非常困難。 我們將要討論的線程接口來自 POSIX.1-2001。線程接口也稱為“pthread”或“POSIX 線程”。 POSIX 線程的功能測試宏是 _POSIX_THREADS。應用程序可以把這個宏用于 #ifdef 測試,從而在編譯時確定是否支持線程,也可以把 _SC_THREADS 常數用于調用 sysconf 函數,進而在運行時確定是否支持線程。遵循 SUSv4 的系統定義符號 _POSIX_THREADS 的值為 200809L。 查看 /usr/include/i386-linux-gnu/bits/posix_opt.h 可以看到 _POSIX_THREADS 的定義 69 /* Tell we have POSIX threads. */70 #define _POSIX_THREADS 200809L使用 pthread 需要包含一個頭文件:pthread.h 同時連一個共享庫:libpthread.so ?即 gcc?編譯時加選項 -lpthread #include <pthread.h>gcc ... -lpthread三、線程標識
就像每個進程有一個進程 ID 一樣,每個線程也有一個線程 ID。進程 ID 在整個系統中是唯一的,但線程 ID 不同,線程 ID 只有在它所屬的進程上下文中才有意義。 線程 ID 是用 pthread_t 數據類型表示的. 查看?/usr/include/i386-linux-gnu/bits/pthreadtypes.h 可以看到 pthread_t 類型為無符號長整型typedef unsigned long int pthread_t; 實現的時候可以用一個結構來代表 pthread_t 數據類型,所以可移植的操作系統實現不能把它作為整數處理。 因此必須使用一個函數來對兩個線程 ID 進行比較。 參看:Pthreads and Semaphores1、函數 pthread_equal
#include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2); 返回值:若相等,返回非 0 數值;否則,返回 0(1)函數功能
比較線程 ID(2)示例說明
//示例一 #include <stdio.h> #include <stdlib.h> #include <pthread.h> int main(){ pthread_t thread_id; thread_id=pthread_self(); // 返回調用線程的線程ID printf("Thread ID: %lu.\n",thread_id); if (pthread_equal(thread_id,pthread_self())) { printf("Equal!\n"); } else { printf("Not equal!\n"); } return 0; } 編譯:# gcc test.c -lpthread 輸出結果: Thread ID: 3075704512. Equal!//示例二 #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> #include <unistd.h>void* routine1 (void* arg) {pthread_t thread = pthread_self ();printf ("子線程1 ID:%lu\n", thread);return (void*)thread; }void* routine2 (void* arg) {pthread_t thread = pthread_self ();printf ("子線程2 ID:%lu\n", thread);if (pthread_equal (thread, (pthread_t)arg))printf ("兩個線程ID相同\n");elseprintf ("兩個線程ID不同\n");return (void*)thread; }int main() {pthread_t thread1;int error = pthread_create (&thread1, NULL, routine1, NULL);if (error){perror ("pthread_create");exit (EXIT_FAILURE);}pthread_join (thread1, NULL);pthread_t thread2;error = pthread_create (&thread2, NULL, routine2, (void*)&thread1);if (error){perror ("pthread_create");exit (EXIT_FAILURE);}//sleep (1);pthread_join (thread2, NULL);return 0; } 輸出結果: 子線程1 ID:3076029248 子線程2 ID:3076029248 兩個線程ID不同(3)示例解析
pthread_equal 函數比較兩個線程 ID,這個沒什么可講的。? 示例二是我講完以后又添加的,創建了兩個子線程。它們的線程 ID 不同,可以理解。 我要說的是 sleep 和 pthread_join 有什么區別呢? 還有為什么在線程中使用 usleep 不合適?? 留個疑問,后續我們講多線程會一起講到的!!2、函數 pthread_self
#include <pthread.h> pthread_t pthread_self(void); 返回值:調用線程的線程 ID(1)函數功能
獲取線程自身的 ID(2)示例說明
當線程需要識別以線程 ID 作為標識的數據結構時,pthread_self 函數可以與 pthread_equal 函數一起使用,如上例 #include <pthread.h> #include <stdio.h>void* thread_func(void *arg) {printf("thread id=%lu\n", pthread_self());return arg; }int main(void) {pid_t pid;pthread_t tid;pid = getpid();printf("process id=%lu\n", pid);pthread_create(&tid, NULL, thread_func, NULL);pthread_join(tid,NULL);return 0; } 輸出結果: process id=2933 thread id=3076057920(3)示例解析
創建線程,查看線程自身的線程 ID四、線程創建
1、函數 pthread_create
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg); 返回值:若成功,返回 0;否則,返回錯誤編號(1)函數功能
創建新線程(2)參數解析
thread:輸出線程 ID。pthread_t 即 unsigned long int。 attr:線程屬性,NULL 表示缺省屬性。pthread_attr_t 可能是整型也可能是結構體,因實現而異。start_routine:線程過程函數指針。參數和返回值的類型都是 void*。啟動線程過程其實就是調用一個函數,只不過是在一個獨立的線程中調用,函數一旦返回,線程即告結束。 arg:傳遞給線程過程函數的參數。線程過程函數的調用者是系統內核,因此需要預先將參數存儲到系統內核中。
(3)函數解析
main 函數可以被視為主線程的線程過程函數。main函數一旦返回,主線程即告結束。主線程一旦結束,進程即告結束。進程一旦結束,其所有的子線程統統結束。 應設法保證在線程過程函數執行期間,傳遞給它的參數 arg 所指向的目標持久有效。注意,pthread 函數在調用失敗時通常會返回錯誤碼,它們并不像其他的 POSIX 函數一樣設置 errno。每個線程都提供 errno 的副本,這只是為了與使用 errno 的現有函數兼容。子啊線程中,從函數中返回錯誤碼更為清晰整潔,不需要依賴那些隨著函數執行不斷變化的全局狀態,這樣可以把錯誤的范圍限制在引起出錯的函數中。
(4)示例說明?
#include <stdio.h> #include <stdlib.h> #include <pthread.h> void *task (void* arg) {printf ("進入子線程\n");sleep (5);printf ("子線程進程ID是:%lu\n", getpid ());printf ("22子線程ID是:%lu\n", pthread_self ());printf ("退出子線程\n"); }int main (void) {printf ("主線程啟動\n");pthread_t thread;int error = pthread_create (&thread, NULL, task, NULL);if (error)perror ("pthread_create"), exit (1);sleep (10);printf ("退出主線程\n");printf("11子線程ID是:%lu\n",thread);printf ("主線程進程ID是:%lu\n", getpid ());printf("主線程ID是:%lu\n",pthread_self());return 0; } 編譯:# gcc test.c -lpthread 輸出結果: 主線程啟動 進入子線程 子線程進程ID是:3117 22子線程ID是:3076180800 退出子線程 退出主線程 11子線程ID是:3076180800 主線程進程ID是:3117 主線程ID是:3076183744(5)示例解析
主線程需要等待子線程結束后再結束,如果不等待不如把上例的 sleep (10); 注釋掉,則不執行子線程。 子線程和主線程是同一個進程,thread 為輸出子線程 ID,pthread_self函數可以得到線程自身的線程ID 再有?pthread 函數在調用失敗時通常會返回錯誤碼。五、線程的等待
1、函數 pthread_join
#include <pthread.h> int pthread_join(pthread_t thread, void **retval); 返回值:成功返回 0,失敗返回錯誤號(1)函數功能
主要用于等待一個線程的結束,并且回去退出碼(2)參數解析
第一個參數:線程的 ID 第二個參數:二級指針,用于獲取線程的退出碼(3)函數解析
該函數根據參數 thread 指定的線程進程等待,將目標線程終止時的退出狀態信息拷貝到 *retval 這個參數指定的位置上。pthread_join()函數,以阻塞的方式等待thread指定的線程結束。當函數返回時,被等待線程的資源被收回。如果線程已經結束,那么該函數會立即返回。并且thread指定的線程必須是 joinable 的。
(4)示例說明
//示例一 #include <stdio.h> #include <pthread.h>void *task (void *p) {//ps 指向只讀常量區 ps本身在棧區char *ps = "hello";return ps; }int main (void) {//啟動一個線程pthread_t tid;pthread_create (&tid, NULL, task, NULL);//主線程等待子線程的處理,并且獲取返回值char *pc = NULL;//pc 指針指向了 上面字符串的首地址pthread_join (tid, (void**)&pc);printf ("pc = %s\n", pc);return 0; } 輸出結果: pc = hello//示例二 //使用pthread_join函數獲取線程的返回值 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h>void* task(void* p) {int i = 0;//靜態局部變量,生命周期變長static int sum = 0;for(i = 1; i <= 100; i++){sum += i;}return (void*)∑ }int main(void) {//1.啟動一個線程pthread_t tid;pthread_create(&tid,NULL,task,NULL);//2.主線程進行等待,獲取返回值int* pi = NULL;pthread_join(tid,(void**)&pi);printf("子線程返回的數據是:%d\n",*pi);return 0; } 輸出結果: 子線程返回的數據是:5050(5)示例解析
上例中主要需要注意的是 pthread_join 獲取返回值。 從線程過程函數中返回值的方法: 線程過程函數將所需返回的內容放在一塊內存中,返回該內存的地址,同時保證這塊內存,在函數返回即線程結束以后依然有效。 若 retval 參數非 NULL,則 pthread_join 函數將線程過程函數所返回的指針,拷貝到該參數所指向的內存中。 若線程過程函數所返回的指針指向動態分配的內存,則還需保證在用過該內存之后釋放它。2、函數 pthread_detach
#include <pthread.h> int pthread_detach(pthread_t thread); 返回值:成功返回 0;失敗返回一個錯誤碼(1)函數功能
主要用于將參數指定的線程標記為分離狀態,對于分離狀態的線程來說:當該線程終止后,會自動將資源釋放給系統,不需要其他線程的加入/等待,也就是說分離的線程無法被其他線程使用 pthread_join 進行等待。 建議:對于新啟動的線程來說,要么使用 pthread_detach 設置為分離狀態,要么使用 pthread_join 設置為可加狀態。(2)示例說明
#include <stdio.h> #include <pthread.h>void *task (void *p) {int i = 0;for (i = 0; i <= 10; i++)printf ("子線程中:i = %d\n", i); } int main (void) {//啟動一個子線程,打印1~10之間的數pthread_t tid;pthread_create (&tid, NULL, task, NULL);//設置子線程為分離的狀態pthread_detach (tid);//主線程進行等待,然后打印1~10之間的數pthread_join (tid, NULL);int i = 0;for (i = 1; i <=10; i++)printf ("主線程中:i = %d\n", i);return 0; } 輸出結果: 主線程中:i = 1 主線程中:i = 2 主線程中:i = 3 主線程中:i = 4 主線程中:i = 5 主線程中:i = 6 主線程中:i = 7 主線程中:i = 8 主線程中:i = 9 主線程中:i = 10(3)示例解析
我們上面有講到,如果使用 pthread_join 等待,則等待子線程完成后打印主線程1~10之間的數。 但是使用了 pthread_detach 分離,則直接打印主線程1~10之間的數,且?pthread_join 等待也會失效。六、線程終止
如果進程中的任意線程調用了 exit、_Exit 或者 _exit,那么整個進程就會終止。與此相比類似,如果默認的動作是終止進程,那么發送到線程的信號就會終止整個進程。 單個線程可以通過 3 種方式退出,因此可以在不終止整個進程的情況下,停止它的控制流。 (1)線程可以簡單地從啟動例程中返回,返回值是線程的退出碼。 (2)線程可以被同一進程中的其他線程取消。 (3)線程調用 pthread_exit。1、我們講一下第三種調用 pthread_exit。
#include <pthread.h> void pthread_exit(void *retval);(1)函數功能
主要用于終止正在運行的線程,通過參數 retval 來帶出線程的退出狀態信息。 在同一個進程中的其他線程可以通過調用 pthread_join 函數來獲取退出狀態信息。(2)函數解析
在線程過程函數或者被線程過程函數直接或間接調用的函數中,調用 pthread_exit 函數,其效果都與在線程過程函數中執行 return 語句效果一樣 -- 終止調用線程。 注意,在任何線程中調用 exit 函數,被終止的都是進程。當然隨著進程的終止,隸屬于該進程的包括調用線程在內的所有線程也都一并終止。(3)示例說明
//示例一 #include <stdio.h> #include <pthread.h> #include <stdlib.h>void *task (void *p) {int i = 0;for (i = 1; i < 100; i++){if (i == 10){//return (void*)i;pthread_exit ((void*)i);//exit (100);}printf ("子線程中:i = %d\n", i);} }int main (void) {pthread_t tid;pthread_create (&tid, NULL, task, NULL);int res = 0;pthread_join (tid, (void**)&res);printf ("res = %d\n", res);return 0; } 編譯:# gcc test.c -lpthread輸出結果: 子線程中:i = 1 子線程中:i = 2 子線程中:i = 3 子線程中:i = 4 子線程中:i = 5 子線程中:i = 6 子線程中:i = 7 子線程中:i = 8 子線程中:i = 9 res = 10//示例二 #include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <string.h> #include <unistd.h>void circle_area (double r) {double* s = malloc (sizeof (double));*s = 3.14 * r * r;pthread_exit (s); }void* start_routine (void* arg) {circle_area (*(double*)arg);printf("此句將不被執行");return NULL; }int main() {double r = 10.0;pthread_t thread;int error = pthread_create (&thread, NULL, start_routine, (void*)&r);if (error){perror ("pthread_create");exit (EXIT_FAILURE);}double* s;if (pthread_join (thread, (void**)&s)){perror ("pthread_join");exit (EXIT_FAILURE);}printf ("圓面積:%g\n", *s);free (s);return 0; } 輸出結果: 圓面積:314(4)示例解析
該示例說明了,在任何線程中調用 exit 函數,被終止的都是進程。進程終止可其他線程就就同樣終止了。 使用 pthread_join 獲取退出狀態信息,return 可以返回退出狀態信息,pthread_exit 同樣也可以返回退出狀態信息。七、線程取消
1、函數 pthread_cancel
#include <pthread.h> int pthread_cancel(pthread_t thread); 返回值:成功返回 0;失敗返回錯誤碼(1)函數功能
主要用于對參數指定的線程發送取消的請求。(2)函數解析
該函數只是向線程發出取消請求,并不等于線程終止。缺省情況下,線程在收到取消請求以后,并不會立即終止,而是仍繼續運行,直到其達到某個取消點。在取消點處,線程檢查其自身是否已被取消,若是則立即終止。 當線程調用一些特定函數時,取消點會出現。2、函數 pthread_setcancelstate
#include <pthread.h> int pthread_setcancelstate(int state, int *oldstate); 返回值:成功返回 0,失敗返回錯誤碼(1)函數功能
主要用于設置新的取消狀態,返回之前的取消狀態。(2)參數解析
state:取消狀態,可取以下值 ? ? PTHREAD_CANCEL_ENABLE ?接受取消請求(缺省) ? ? PTHREAD_CANCEL_DISABLE ?忽略取消請求 oldstate:輸出原取消狀態,可取 NULL3、函數 pthread_setcanceltype
#include <pthread.h> int pthread_setcanceltype(int type, int *oldtype); 返回值:成功返回 0,;失敗返回錯誤碼(1)函數功能
主要用于設置新的取消類型,獲取之前的取消類型(2)參數解析
type:取消類型,可取以下值 ? ? PTHREAD_CANCEL_DEFERRED ?延遲取消(缺省) ? ? ? ? 被取消線程接收到取消請求之后并不立即終止,而是一直等到執行了特定的函數(取消點)之后再終止。 ? ? PTHREAD_CANCEL_ASYNCHRONOUS ?異步取消 ? ? ? ? 被取消線程可以在任意時刻終止,而不是非得遇到取消點。 oldtype:輸出原取消類型,可取 NULL4、示例說明
//線程取消函數的使用 #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h>void* task(void* p) {//設置允許被取消pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//設置為立即取消pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,NULL);while(1){printf("I am superman!\n");sleep(1);} }void* task2(void* p) {printf("開始取消線程...\n");sleep(5);printf("取消線程結束\n");pthread_cancel(*(pthread_t*)p); }int main(void) {//1.啟動一個新線程,不斷進行打印pthread_t tid;pthread_create(&tid,NULL,task,NULL);//2.啟動另外一個新線程,負責取消上述線程pthread_t tid2;pthread_create(&tid2,NULL,task2,&tid);//3.主線程等待子線程的結束pthread_join(tid,NULL);pthread_join(tid2,NULL);return 0; }編譯:# gcc test.c -lpthread輸出結果: 開始取消線程... I am superman! I am superman! I am superman! I am superman! I am superman! 取消線程結束5、示例解析
創建了兩個線程,線程 2 負責取消線程 1。而設置取消狀態為允許被取消,取消類型是立即取消。八、線程清理處理程序
1、函數 pthread_cleanup_push、pthread_cleanup_pop
#include <pthread.h> void pthread_cleanup_push(void (*routine)(void *), void *arg); void pthread_cleanup_pop(int execute);(1)函數解析
與進程在退出時可用 atexit 函數安排退出時類似的,線程也可以安排它退出時需要調用的函數。這樣的函數就稱為 線程清理處理程序。一個線程可以建立多個清理處理程序。處理程序記錄在棧中,也就是說,它們的執行順序與它們注冊時相反。(2)參數解析
當線程執行以下動作時,清理函數 routine 是由 pthread_cleanup_push 函數調度的,調用時只有一個參數 arg: 調用 pthread_exit 時; 響應取消請求時; 用非零 execute 參數設置 0,清理函數將不被調用。 不管發生上述哪種情況,pthread_cleanup_pop 都將刪除 pthread_cleanup_push 調用建立的清理處理程序。(3)示例說明
#include "apue.h" #include <pthread.h>void cleanup(void *arg) {printf("cleanup: %s\n", (char *)arg); }void * thr_fn1(void *arg) {printf("thread 1 start\n");pthread_cleanup_push(cleanup, "thread 1 first handler");pthread_cleanup_push(cleanup, "thread 1 second handler");printf("thread 1 push complete\n");if (arg)return((void *)1);pthread_cleanup_pop(0);pthread_cleanup_pop(0);return((void *)1); }void * thr_fn2(void *arg) {printf("thread 2 start\n");pthread_cleanup_push(cleanup, "thread 2 first handler");pthread_cleanup_push(cleanup, "thread 2 second handler");printf("thread 2 push complete\n");if (arg)pthread_exit((void *)2);pthread_cleanup_pop(0);pthread_cleanup_pop(0);pthread_exit((void *)2); }int main(void) {int err;pthread_t tid1, tid2;void *tret;err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);if (err != 0)err_exit(err, "can't create thread 1");err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);if (err != 0)err_exit(err, "can't create thread 2");err = pthread_join(tid1, &tret);if (err != 0)err_exit(err, "can't join with thread 1");printf("thread 1 exit code %ld\n", (long)tret);err = pthread_join(tid2, &tret);if (err != 0)err_exit(err, "can't join with thread 2");printf("thread 2 exit code %ld\n", (long)tret);exit(0); } 編譯:# gcc test.c -lpthread輸出結果: thread 2 start thread 2 push complete cleanup: thread 2 second handler cleanup: thread 2 first handler thread 1 start thread 1 push complete thread 1 exit code 1 thread 2 exit code 2(4)示例解析
兩個線程都正確的啟動和退出了,但是只有第二個線程的清理處理程序被調用了,而區別是一個是 return 返回,一個為調用 pthread_exit。因此,可以看出如果線程是通過從它的啟動例程中返回而終止的話,它的清理處理程序就不會被調用。還要注意,清理處理程序時按照與它們安裝時相反的順序被調用的。總結
以上是生活随笔為你收集整理的UNIX再学习 -- 线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: @RequestBody、@Respon
- 下一篇: 深入理解simhash原理