使用線程的好處:(1)爲每種事件分配單獨的線程、可以簡化處理異步事件的代碼;(2)多個線程自動地能夠訪問相同的存儲地址空間和文件描述符;(3)將一個問題分解爲多個程序,改善整個程序的吞吐量;(4)使用多線程改善交互程序的響應時間。數據結構
進程與線程關係:進程是系統中程序執行和資源分配的基本單位。每一個進程有本身的數據段、代碼段和堆棧段。線程一般叫作輕型的進程。線程是在共享內存空間中併發執行的多道執行路徑,他們共享一個進程的資源。由於線程和進程比起來很小,因此相對來講,線程花費更少的CPU資源。多線程
一、線程標識併發
進程ID在整個系統中時惟一的,但線程ID只在它所屬的進程環境中有效。線程ID用pthread_t數據類型來表示,實現的時候用一個結構來表明pthread_t數據類型。線程ID操做函數以下:異步
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);//比較兩個線程ID
pthread_t pthread_self(void); //獲取調用線程IDide
二、線程建立函數
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);post
thread指向的內存單元被設置爲新建立線程的ID,attr用於指定線程屬性,start_routine是新線程開始執行的函數地址,arg是函數參數。若是是多個參數,能夠將把參數存放在一個結構中,而後將結構地址傳遞給arg。線程建立時不能保證哪一個線程會先運行。寫個程序建立一個線程,輸出線程標識符,程序以下:測試
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 8 pthread_t ntid; 9 void printpthreadinfo(const char* str); 10 void* thread_func(void *arg); 11 12 int main() 13 { 14 int err; 15 //建立一個新線程 16 err = pthread_create(&ntid,NULL,thread_func,NULL); 17 if(err != 0) 18 { 19 perror("pthread_create()"); 20 exit(-1); 21 } 22 printpthreadinfo("main thread: "); 23 sleep(2); //給新線程時間執行 24 return 0; 25 } 26 void printpthreadinfo(const char* str) 27 { 28 pid_t pid; 29 pthread_t tid; 30 pid = getpid(); 31 tid = pthread_self(); 32 printf("%s pid %u tid %u (0x%x)\n",str,(unsigned int)pid,(unsigned int)tid,(unsigned int)tid); 33 } 34 void* thread_func(void *arg) 35 { 36 printpthreadinfo("new thread: "); 37 return ((void*) 0); 38 }
因爲pthread庫不是Linux系統默認的庫,鏈接時須要使用庫libpthread.a,因此在使用pthread_create建立線程時,在編譯中要加-lpthread參數。執行結果以下:spa
三、線程終止線程
若是進程中的任一個線程調用了exit、_exit、_Exit函數,那麼整個進程就會終止。單個線程終止方式:(1)線程只是從啓動例程中返回,返回值是線程的退出碼;(2)線程能夠被同一進程的其餘線程取消;(3)線程調用pthread_exit函數。線程終止函數原型以下:
void pthread_exit(void *retval); //retval終止狀體
int pthread_join(pthread_t thread, void **retval); //獲取線程終止狀態
其餘線程經過調用pthread_join函數獲取線程終止狀態,調用線程將一直阻塞,直到指定的線程調用pthread_exit、從啓動例程中返回或者被取消。寫個程序建立兩個線程,獲取線程退出狀態。程序以下:
1 #include <pthread.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #include <unistd.h> 5 #include <errno.h> 6 #include <sys/types.h> 7 8 void* thread_func1(void *arg); 9 void* thread_func2(void *arg); 10 11 int main() 12 { 13 pthread_t tid1; 14 pthread_t tid2; 15 int err; 16 void *tret; 17 //建立新線程1 18 err = pthread_create(&tid1,NULL,thread_func1,NULL); 19 if(err != 0) 20 { 21 perror("pthread_create() error"); 22 exit(-1); 23 } 24 //建立新線程2 25 err = pthread_create(&tid2,NULL,thread_func2,NULL); 26 if(err != 0) 27 { 28 perror("pthread_create() error"); 29 exit(-1); 30 } 31 //等待線程1終止 32 err = pthread_join(tid1,&tret); 33 if(err != 0) 34 { 35 perror("pthread_join error"); 36 exit(-1); 37 } 38 printf("thread1 exit code %d\n",(int)tret); 39 //等待線程2終止 40 err = pthread_join(tid2,&tret); 41 if(err != 0) 42 { 43 perror("pthread_join error"); 44 exit(-1); 45 } 46 printf("thread2 exit code %d\n",(int)tret); 47 exit(0); 48 } 49 void* thread_func1(void *arg) 50 { 51 printf("thread1 is returning.\n"); 52 return ((void*)1); 53 } 54 void* thread_func2(void *arg) 55 { 56 printf("thread2 exiting.\n"); 57 pthread_exit((void*)2); 58 }
程序執行結果以下:
須要注意的是pthread_create和pthread_exit函數的無類型指針參數可以傳遞的數值能夠不止一個,該指針能夠傳遞包含複雜信息的結構地址,這個結構必須所使用的內存必須在調用者用完之後必須仍然有效,不然會出現沒法訪問或非法。例如在線程的棧上分配了該結構,例以下面程序,參數不正確使用。
線程調用pthread_cancel函數來請求取消同一進程中的其餘線程,並不等待線程終止,只是提出請求而已。函數原型爲 int pthread_cancel(pthread_t tid)。函數功能等價於使得tid標識的線程調用pthread_exit(PTHREAD_CANCELED)。
線程清理處理程序,相似進程退出時候清理函數,線程能夠創建多個清理處理程序,處理程序記錄在棧中,執行順序與註冊順序相反。函數原型以下:
void pthread_cleanup_push(void (*routine)(void *),void *arg); //註冊清理函數
void pthread_cleanup_pop(int execute); //刪除清理程序,若execute=0,清理函數將不被調用
兩個函數限制:必須在線程相同的做用域中以匹配隊的形式使用
清理函數在如下三種狀況會調用:(1)調用pthread_exit;(2)響應取消請求;(3)用非零execute參數調用pthread_cleanup_pop。pthread_cleanup_pop函數刪除上次pthread_cleanup_push調用創建的清理處理程序。
寫個程序測試調用線程清理程序,程序以下:
程序執行結果以下:
從結果能夠看出線程1的清理處理程序沒有被調用,線程2的清理處理程序調用序列與註冊序列相反。
進程原語與線程原語的比較
進程原語 | 線程原語 | 描述 |
fork | pthread_create | 建立新的控制流 |
exit | pthread_exit | 從現有的控制流中退出 |
waitpid | pthread_join | 從控制流中獲得退出狀態 |
atexit | pthread_cleanup_push | 註冊在退出控制流時調用的函數 |
getpid | pthread_self | 獲取控制流的ID |
abort | pthread_cancel | 請求控制流的非正常退出 |
四、線程同步
當多個控制線程共享相同的內存時,須要確保每一個線程看到一致的數據視圖。只有多個線程存在同時讀寫同一變量時,須要對線程進行同步 。線程同步的方法:線程鎖(互斥量)、讀寫鎖、條件變量。
若是對多個線程訪問共同數據時,不加同步控制,會出什麼問題呢?以下程序,兩個線程對一個共享的數據結構進行操做,結果是不肯定的。、
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 8 struct foo 9 { 10 int f_count; 11 }; 12 int iss = 0; 13 struct foo* foo_alloc(void); 14 void foo_add(struct foo *fp); 15 void foo_release(struct foo *fp); 16 #define SS_COUNT 1000 17 void * thread_func1(void *arg); 18 void * thread_func2(void *arg); 19 int main() 20 { 21 pthread_t pid1,pid2; 22 int err; 23 void *pret; 24 struct foo *fobj; 25 fobj = foo_alloc(); 26 //建立新線程1,函數地址爲thread_fun1,參數爲fobj 27 err = pthread_create(&pid1,NULL,thread_func1,(void*)fobj); 28 if(err != 0) 29 { 30 perror("pthread_create() error"); 31 exit(-1); 32 } 33 ////建立新線程2,函數地址爲thread_fun2,參數爲fobj 34 err = pthread_create(&pid2,NULL,thread_func2,(void*)fobj); 35 if(err != 0) 36 { 37 perror("pthread_create() error"); 38 exit(-1); 39 } 40 //等待線程退出 41 pthread_join(pid1,&pret); 42 printf("thread 1 exit code is: %d\n",(int)pret); 43 pthread_join(pid2,&pret); 44 printf("thread 2 exit code is: %d\n",(int)pret); 45 printf("...%d\n",iss); 46 exit(0); 47 } 48 //初始化 49 struct foo* foo_alloc(void) 50 { 51 struct foo *fobj; 52 fobj = (struct foo*)malloc(sizeof(struct foo)); 53 if(fobj != NULL) 54 fobj->f_count = 0; 55 return fobj; 56 } 57 void foo_add(struct foo *fp) 58 { 59 fp->f_count++; 60 iss = fp->f_count; 61 //printf("add = %d\n",fp->f_count); 62 } 63 void foo_release(struct foo *fp) 64 { 65 fp->f_count--; 66 iss = fp->f_count; 67 //printf("adc = %d\n",fp->f_count); 68 } 69 void * thread_func1(void *arg) 70 { 71 struct foo *fp = (struct foo*)arg; 72 int i; 73 printf("thread 1 start.\n"); 74 for(i=0;i<=SS_COUNT;i++) 75 foo_release(fp); //數目減小1 76 77 printf("thread 1 exit.\n"); 78 pthread_exit((void*)1); 79 } 80 void * thread_func2(void *arg) 81 { 82 int i; 83 struct foo *fp = (struct foo*)arg; 84 printf("thread 2 start.\n"); 85 for(i=0;i<=SS_COUNT;i++) 86 { 87 foo_add(fp); //數目增長1 88 foo_add(fp); 89 } 90 printf("thread 2 exit.\n"); 91 pthread_exit((void*)2); 92 } 93
執行結果以下:
jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ gcc mutex1.c -lpthread
jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ ./a.out
thread 1 start.
thread 2 start.
thread 1 exit.
thread 2 exit.
thread 1 exit code is: 1
thread 2 exit code is: 2
...1001
//============================================
jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ gcc mutex1.c -lpthread
jiang@jiang-GA-A75M-D2H:~/share/apue.2e$ ./a.out
thread 1 start.
thread 2 start.
thread 1 exit.
thread 2 exit.
thread 1 exit code is: 1
thread 2 exit code is: 2
...1034
//============================================
從結果能夠看出:程序執行兩次結果是不同的,隨即性比較強。線程1和線程2執行順序是不肯定的。須要帶線程1和線程2進行同步控制。
(1)互斥量
mutex是一種簡單的加鎖的方法來控制對共享資源的訪問。在同一時刻只能有一個線程掌握某個互斥上的鎖,擁有上鎖狀態的線程可以對共享資源進行訪問。若其餘線程但願上鎖一個已經被上了互斥鎖的資源,則該線程掛起,直到上鎖的線程釋放互斥鎖爲止。互斥量類型爲pthread_mutex_t。互斥量操做函數以下:
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutex_attr_t *mutexattr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex); //對互斥量進行加鎖
int pthread_mutex_trylock(pthread_mutex_t *mutex); //嘗試對互斥量進行加鎖
int pthread_mutex_unlock(pthread_mutex_t *mutex); //對互斥量進行解鎖
寫個程序練習一個互斥量,對以上程序添加互斥量,建立兩個線程操做一個數據結構,修改公共的數據。程序以下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 8 struct foo 9 { 10 int f_count; 11 pthread_mutex_t f_lock; //互斥量 12 }; 13 14 #define SS_COUNT 10000 15 int iss = 0; 16 17 struct foo* foo_alloc(void); 18 void foo_add(struct foo *fp); 19 void foo_release(struct foo *fp); 20 21 void * thread_func1(void *arg); 22 void * thread_func2(void *arg); 23 int main() 24 { 25 pthread_t pid1,pid2; 26 int err; 27 void *pret; 28 struct foo *fobj; 29 fobj = foo_alloc(); 30 err = pthread_create(&pid1,NULL,thread_func1,(void*)fobj); 31 if(err != 0) 32 { 33 perror("pthread_create() error"); 34 exit(-1); 35 } 36 err = pthread_create(&pid2,NULL,thread_func2,(void*)fobj); 37 if(err != 0) 38 { 39 perror("pthread_create() error"); 40 exit(-1); 41 } 42 pthread_join(pid1,&pret); 43 printf("thread 1 exit code is: %d\n",(int)pret); 44 pthread_join(pid2,&pret); 45 printf("thread 2 exit code is: %d\n",(int)pret); 46 printf("...%d\n",iss); 47 exit(0); 48 } 49 struct foo* foo_alloc(void) 50 { 51 struct foo *fobj; 52 fobj = (struct foo*)malloc(sizeof(struct foo)); 53 if(fobj != NULL) 54 { 55 fobj->f_count = 0; 56 //初始化互斥量 57 if (pthread_mutex_init(&fobj->f_lock,NULL) != 0) 58 { 59 free(fobj); 60 return NULL; 61 } 62 } 63 return fobj; 64 } 65 void foo_add(struct foo *fp) 66 { 67 pthread_mutex_lock(&fp->f_lock); //加鎖 68 fp->f_count++; 69 iss = fp->f_count; 70 //printf("add = %d\n",fp->f_count); 71 pthread_mutex_unlock(&fp->f_lock); //解鎖 72 } 73 void foo_release(struct foo *fp) 74 { 75 pthread_mutex_lock(&fp->f_lock); //加鎖 76 fp->f_count--; 77 iss = fp->f_count; 78 //printf("adc= %d\n",fp->f_count); 79 pthread_mutex_unlock(&fp->f_lock); //解鎖 80 } 81 void * thread_func1(void *arg) 82 { 83 int i; 84 struct foo *fp = (struct foo*)arg; 85 printf("thread 1 start.\n"); 86 for(i=0;i<=SS_COUNT;i++) 87 foo_release(fp); 88 printf("thread 1 exit.\n"); 89 pthread_exit((void*)1); 90 } 91 void * thread_func2(void *arg) 92 { 93 int i; 94 struct foo *fp = (struct foo*)arg; 95 printf("thread 2 start.\n"); 96 for(i=0;i<=SS_COUNT;i++) 97 { 98 foo_add(fp); 99 foo_add(fp); 100 } 101 printf("thread 2 exit.\n"); 102 pthread_exit((void*)2); 103 }
程序執行結果以下:
//==================
thread 1 start.
thread 2 start.
thread 1 exit.
thread 1 exit code is: 1
thread 2 exit.
thread 2 exit code is: 2
...10001
//==================
從結果能夠看出,程序執行屢次結果相同,線程1和線程2同步操做。
(2)讀寫鎖(共享鎖)
讀寫鎖可使讀操做比互斥量有更高的並行性,互斥量要麼是鎖住狀態要麼是不加鎖狀態,並且一次只有一個線程能夠對其加鎖。讀寫鎖有三種狀態:讀模式下加鎖狀態、寫模式下加鎖狀態、不加鎖狀態。一次只有一個線程能夠佔有寫模式讀寫鎖,而多個線程能夠同時佔有度模式的讀寫鎖。當讀操做較多,寫操做較少時,可以使用讀寫鎖提升線程讀併發性。讀寫鎖數據類型爲pthread_rwlock_t,操做函數以下:
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_mutex_t *mutex);
int pthread_rwlock_trywrlock(pthread_mutex_t *mutex);
寫個程序練習讀寫鎖,建立三個線程,兩個線程讀操做,一個線程寫操做。程序以下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 struct foo 8 { 9 int f_count; 10 pthread_rwlock_t f_rwlock; 11 }; 12 struct foo * foo_alloc() 13 { 14 struct foo* fp; 15 fp = (struct foo*)malloc(sizeof(struct foo)); 16 if(fp != NULL) 17 { 18 fp->f_count = 0; 19 pthread_rwlock_init(&fp->f_rwlock,NULL); 20 } 21 return fp; 22 } 23 void foo_add(struct foo *fp) 24 { 25 pthread_rwlock_wrlock(&fp->f_rwlock); 26 fp->f_count++; 27 pthread_rwlock_unlock(&fp->f_rwlock); 28 } 29 void foo_release(struct foo *fp) 30 { 31 pthread_rwlock_wrlock(&fp->f_rwlock); 32 fp->f_count--; 33 if(fp->f_count == 0) 34 { 35 pthread_rwlock_unlock(&fp->f_rwlock); 36 pthread_rwlock_destroy(&fp->f_rwlock); 37 free(fp); 38 } 39 pthread_rwlock_unlock(&fp->f_rwlock); 40 } 41 int foo_search(struct foo *fp) 42 { 43 int count; 44 pthread_rwlock_rdlock(&fp->f_rwlock); 45 count = fp->f_count; 46 pthread_rwlock_unlock(&fp->f_rwlock); 47 return count; 48 } 49 void * thread_func1(void *arg) 50 { 51 struct foo *fp = (struct foo*)arg; 52 printf("thread 1 start.\n"); 53 foo_search(fp); 54 printf("in thread 1 search count = %d\n",fp->f_count); 55 printf("thread 1 exit.\n"); 56 pthread_exit((void*)1); 57 } 58 void * thread_func2(void *arg) 59 { 60 struct foo *fp = (struct foo*)arg; 61 printf("thread 2 start.\n"); 62 foo_add(fp); 63 printf("in thread 2 add count = %d\n",fp->f_count); 64 printf("thread 2 exit.\n"); 65 pthread_exit((void*)2); 66 } 67 void * thread_func3(void *arg) 68 { 69 struct foo *fp = (struct foo*)arg; 70 printf("thread 3 start.\n"); 71 foo_add(fp); 72 printf("in thread 3 add count = %d\n",fp->f_count); 73 foo_search(fp); 74 printf("in thread 3 search count = %d\n",fp->f_count); 75 printf("thread 3 exit.\n"); 76 pthread_exit((void*)3); 77 } 78 79 int main() 80 { 81 pthread_t pid1,pid2,pid3; 82 int err; 83 void *pret; 84 struct foo *fobj; 85 fobj = foo_alloc(); 86 pthread_create(&pid1,NULL,thread_func1,(void*)fobj); 87 pthread_create(&pid2,NULL,thread_func2,(void*)fobj); 88 pthread_create(&pid3,NULL,thread_func3,(void*)fobj); 89 pthread_join(pid1,&pret); 90 printf("thread 1 exit code is: %d\n",(int)pret); 91 pthread_join(pid2,&pret); 92 printf("thread 2 exit code is: %d\n",(int)pret); 93 pthread_join(pid3,&pret); 94 printf("thread 3 exit code is: %d\n",(int)pret); 95 exit(0); 96 }
程序執行結果以下:
(3)條件變量
條件變量給多個線程提供了個會合的機會,條件變量與互斥量一塊兒使用,容許線程以無競爭的方式等待特定的條件發生,條件自己是由互斥量保護。線程在改變條件狀態前必須先鎖住互斥量,條件變量容許線程等待特定條件發生。條件變量經過容許線程阻塞和等待另外一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一塊兒使用。使用時,條件變量被用來阻塞一個線程,當條件不知足時,線程每每解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將從新鎖定互斥鎖並從新測試條件是否知足。通常說來,條件變量被用來進行線承間的同步。條件變量類型爲pthread_cond_t,使用前必須進行初始化,操做函數以下:
int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex,const structtimespec *restrict abstime);
int pthread_cond_broadcast(pthread_cond_t *cond); //喚醒等待該條件的全部線程
int pthread_cond_signal(pthread_cond_t *cond); //喚醒等待該條件的某個線程
寫個程序練習條件變量,程序中建立兩個新線程1和2,線程1使數目增長,線程2使數目減小。只有當數目不爲0時,減小才能進行,爲0時候須要等待。程序以下:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <errno.h> 5 #include <sys/types.h> 6 #include <pthread.h> 7 8 struct foo 9 { 10 int f_count; 11 pthread_mutex_t f_mutex; 12 pthread_cond_t f_cond; 13 }; 14 15 struct foo * foo_alloc() 16 { 17 struct foo* fp; 18 fp = (struct foo*)malloc(sizeof(struct foo)); 19 if(fp != NULL) 20 { 21 fp->f_count = 0; 22 pthread_mutex_init(&fp->f_mutex,NULL); 23 pthread_cond_init(&fp->f_cond,NULL); 24 } 25 return fp; 26 } 27 void foo_increase(struct foo *fp) 28 { 29 pthread_mutex_lock(&fp->f_mutex); 30 if(fp->f_count== 0) //發送信號,通知能夠作decrease操做 31 pthread_cond_signal(&fp->f_cond); 32 fp->f_count++; 33 pthread_mutex_unlock(&fp->f_mutex); 34 } 35 void foo_decrease(struct foo *fp) 36 { 37 pthread_mutex_lock(&fp->f_mutex); 38 while(fp->f_count == 0) //判斷是否能夠作decrease操做 39 pthread_cond_wait( &fp->f_cond, &fp->f_mutex);//等待信號 40 fp->f_count--; 41 pthread_mutex_unlock(&fp->f_mutex); 42 } 43 void * thread_func1(void *arg) 44 { 45 struct foo *fp = (struct foo*)arg; 46 printf("thread 1 start.\n"); 47 foo_increase(fp); 48 printf("in thread 1 count = %d\n",fp->f_count); 49 printf("thread 1 exit.\n"); 50 pthread_exit((void*)1); 51 } 52 void * thread_func2(void *arg) 53 { 54 struct foo *fp = (struct foo*)arg; 55 printf("thread 2 start.\n"); 56 foo_decrease(fp); 57 printf("in thread 2 count = %d\n",fp->f_count); 58 printf("thread 2 exit.\n"); 59 pthread_exit((void*)2); 60 } 61 62 int main() 63 { 64 pthread_t pid1,pid2,pid3; 65 int err; 66 void *pret; 67 struct foo *fobj; 68 fobj = foo_alloc(); 69 pthread_create(&pid1,NULL,thread_func1,(void*)fobj); 70 pthread_create(&pid2,NULL,thread_func2,(void*)fobj); 71 pthread_join(pid1,&pret); 72 printf("thread 1 exit code is: %d\n",(int)pret); 73 pthread_join(pid2,&pret); 74 printf("thread 2 exit code is: %d\n",(int)pret); 75 exit(0); 76 }
程序執行結果以下: