多進程在代碼中並不多見,因為它有很大的局限性,如分配大量資源、進程的PID個數有限等。現在更多地是使用多線程實現代碼並發。
一、線程的概念
線程是一種輕量級的代碼並發技術,對資源的要求較小。線程隸屬於某個進程,進程內部可以使用多線程,線程內部也可以使用多線程。
線程共享進程的資源,不需要太多額外的資源,每個線程只需要額外分配一個棧區即可。
在使用多線程之后,在程序設計時就可以把進程設計成在某一時刻不止做一件事,每個線程處理各自的任務。
我們之前寫的代碼都是單線程的,也就是main()函數線程,又稱主線程。主線程一旦結束,進程也就隨之結束,所有線程也隨之結束。
二、線程標識
和每個進程有一個進程PID一樣,每個線程也有一個線程ID。進程ID不可重復,但是線程ID屬於某個進程,只要在此進程中的線程ID不重復即可。
獲取自身線程ID使用函數為pthread_self(),其函數定義如下:
#include <pthread.h> pthread_t pthread_self(void); Compile and link with -pthread.
函數返回值類型是pthread_t,與進程的pid_t不同,pthread_t實現可能不是整數類型(操作系統不同,類型不同)。因此我們需要使用pthread_equal()函數來對兩個線程ID進行比較,其函數定義如下:
#include <pthread.h> int pthread_equal(pthread_t t1, pthread_t t2); Compile and link with -pthread.
該函數若t1和t2線程ID相等,返回非0數值;否則,返回0。
Linux和Unix都在POSIX規范中對線程有了定義,主要是一個頭文件pthread.h + 一個共享庫libpthread.so。所有線程相關的函數、結構體和類型一般以pthread_開頭,比如接下來要講解的線程創建函數pthread_create()。
由於使用共享庫,在編譯鏈接時,需要加上-pthread或-lpthread
三、線程創建
新增的線程可以通過pthread_create()函數創建。其函數聲明如下:
#include <pthread.h> int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
函數參數以及返回值:
thread:返回的線程ID
attr:線程的屬性,一般給0即可
start_rountine:線程執行函數指針
arg:線程執行函數的函數參數
返回值:成功返回0;失敗返回錯誤碼,需要使用strerror()轉換為錯誤信息
示例代碼如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 #define PI 3.141592 6 7 void *task(void *p) 8 { 9 int *r = (int *)p; 10 printf("半徑為%d圓面積為:%lf\n", *r, PI*(*r)*(*r)); 11 } 12 13 int main() 14 { 15 pthread_t id; 16 17 int x = 10; 18 pthread_create(&id, 0, &task, &x); 19 sleep(1); // 防止主線程先結束 20 21 return 0; 22 }
四、線程終止
如果進程中任意線程調用了exit()、_Exit()或_exit(),那么整個進程就會終止。
因此,線程需要有自己的終止方式:
1. 線程的函數執行了return或exit()。
2. 調用線程終止函數pthread_exit(void *),返回值做為終止參數
3. 線程被其它線程終止
pthread_exit()函數聲明如下:
#include <pthread.h> void pthread_exit(void *retval);
retval參數與傳給pthread_create()參數類似。進程中的其他線程可以通過pthread_join()函數訪問到這個指針,其函數聲明如下:
#include <pthread.h> int pthread_join(pthread_t thread, void **retval);
函數參數以及返回值:
thread:指定的線程ID
retval:pthread_exit()函數的返回值
返回值:成功返回0;失敗返回錯誤碼,需要使用strerror()轉換為錯誤信息
pthread_join()函數會導致調用線程一直阻塞,直到指定的線程調用pthread_exit()、從線程中返回或被取消。
對於取消線程,我們可以使用pthread_cancel()函數,其函數聲明如下:
#include <pthread.h>
int pthread_cancel(pthread_t thread);
示例代碼如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 void *task(void *p) 6 { 7 int *r = (int *)p; 8 int area = 0; // 只有int和void *可以互轉 9 10 area = (*r) * (*r); 11 12 return (void *)area; // 不能返回局部變量的地址 13 } 14 15 int main() 16 { 17 pthread_t id; 18 19 int x = 10; 20 pthread_create(&id, 0, &task, &x); 21 int area; 22 pthread_join(id, (void**)&area); // 取地址即可 23 printf("長度為%d正方形面積為:%d\n", x, area); 24 25 return 0; 26 }
在默認情況下,線程的終止狀態會保存直到對該線程調用pthread_join()。如果線程已經被分離,線程的底層存儲資源可以在線程終止時被立即收回。在線程被分離后,就不能用pthread_join()函數等待它的終止狀態,因為對分離狀態的線程調用pthread_join()會產生定義行為。可以調用pthread_detach()分離線程。
#include <pthread.h>
int pthread_detach(pthread_t thread);
示例代碼如下:
1 #include <stdio.h> 2 #include <pthread.h> 3 #include <unistd.h> 4 5 void *task(void *p) 6 { 7 int i; 8 for(i = 0; i < 30; i++) 9 { 10 printf("task:%d\n", i); 11 usleep(10000); 12 } 13 } 14 15 int main() 16 { 17 pthread_t id; 18 pthread_create(&id, 0, task, 0); 19 pthread_detach(id); 20 // pthread_join(id, 0); // 沒有阻塞等待效果 21 int i; 22 for(i = 0; i < 30; i++) 23 { 24 printf("main:%d\n", i); 25 usleep(10000); 26 } 27 28 return 0; 29 }
執行此代碼,可以發現主線程和task線程同時執行,而不是主線程阻塞等待task線程執行完成。
五、線程同步
使用線程並發並發就會導致一個問題:假設線程A對一個文件寫入3000個字符“a”,而另一個線程B對這個文件寫入3000個“b”,第三個線程C讀取這個文件,會導致讀取數據不一定是什么。
因為可能在一段時間內先執行了A;當A執行到一半CPU切換到執行B了,這時就會導致數據混亂。
為了保證數據一致性,我們需要保證線程同步。也就是讓線程在同一時間只允許一個線程訪問該變量。
一般采用的方式有原子操作、自旋鎖、信號量和互斥體。具體細節可查看:五、並發控制
線程對應的函數是以上函數的變體(一般是在以上函數前面加上pthread_,如自旋鎖初始化函數pthread_spin_lock())。
讀寫鎖:
讀寫鎖與互斥量類似,不過讀寫鎖允許更高的並行性。
其系列函數定義如下:
#include <pthread.h> /* 初始化讀寫鎖 */ 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_tryrdlock(pthread_rwlock_t *rwlock); /* 寫鎖 */ int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); /* 解鎖 */ int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
條件變量:
條件變量使我們睡眠等待某種條件出現,然后線程才能繼續執行,與信號(signal)類似。它必須與互斥量配合使用。
其系列函數定義如下:
#include <pthread.h> /* 初始化條件變量 */ 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_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond); /* 等待條件變量 */ int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
自旋鎖:
其系列函數定義如下:
#include <pthread.h> /* 自旋鎖初始化 */ int pthread_spin_init(pthread_spinlock_t *lock, int pshared); /* 自旋鎖注銷 */ int pthread_spin_destroy(pthread_spinlock_t *lock); /* 上鎖/解鎖 */ int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock);
互斥量:
其系列函數定義如下:
#include <pthread.h> /* 初始化互斥量 */ int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); /* 銷毀互斥量 */ 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 <pthread.h> 3 #include <unistd.h> 4 5 char *name[5]; 6 int index; 7 8 pthread_mutex_t lock; // 1 聲明 9 10 void *task(void *p) 11 { 12 pthread_mutex_lock(&lock); // 3 上鎖 13 name[index] = (char*)p; 14 usleep(10000); 15 index++; 16 pthread_mutex_unlock(&lock); // 5 解鎖 17 } 18 19 int main() 20 { 21 name[index] = "nihao"; 22 index++; 23 pthread_mutex_init(&lock, 0); // 2 初始化 24 pthread_t id1, id2; 25 pthread_create(&id1, 0, task, "hello"); 26 pthread_create(&id2, 0, task, "world"); // 兩個進程運行,id1剛執行,id2也開始執行,暫停,id2覆蓋id1 27 pthread_join(id1, 0); 28 pthread_join(id2, 0); 29 pthread_mutex_destroy(&lock); // 6 銷毀 30 int i; 31 for(i = 0; i < index; i++) 32 printf("%s\n", name[i]); // 空指針 33 34 return 0; 35 }
下一章 第十四章:高級I/O