線程按照其調度者可以分為用戶級線程和核心級線程兩種
用戶級線程主要解決的是上下文切換的問題,它的調度算法和調度過程全部由用戶自行選擇決定,在運行時不需要特定的內核支持;
我們常用基本就是用戶級線程,所以就只總結一下POSIX提供的用戶級線程接口;
基本線程操作相關的函數:
1線程的建立結束
2線程的互斥和同步
3使用信號量控制線程
4線程的基本屬性配置
基本線程操作:
函數 | 說明 |
---|---|
pthread_create() | 創建線程開始運行相關線程函數,運行結束則線程退出 |
pthread_eixt() | 因為exit()是用來結束進程的,所以則需要使用特定結束線程的函數 |
pthread_join() | 掛起當前線程,用於阻塞式地等待線程結束,如果線程已結束則立即返回,0=成功 |
pthread_cancel() | 發送終止信號給thread線程,成功返回0,但是成功並不意味着thread會終止 |
pthread_testcancel() | 在不包含取消點,但是又需要取消點的地方創建一個取消點,以便在一個沒有包含取消點的執行代碼線程中響應取消請求. |
pthread_setcancelstate() | 設置本線程對Cancel信號的反應 |
pthread_setcanceltype() | 設置取消狀態 繼續運行至下一個取消點再退出或者是立即執行取消動作 |
pthread_setcancel() | 設置取消狀態 |
互斥與同步機制基本函數
函數 | 說明 |
---|---|
pthread_mutex_init() | 互斥鎖的初始化 |
pthread_mutex_lock() | 鎖定互斥鎖,如果嘗試鎖定已經被上鎖的互斥鎖則阻塞至可用為止 |
pthread_mutex_trylock() | 非阻塞的鎖定互斥鎖 |
pthread_mutex_unlock() | 釋放互斥鎖 |
pthread_mutex_destory() | 互斥鎖銷毀函數 |
信號量線程控制(默認無名信號量)
函數 | 說明 |
---|---|
sem_init(sem) | 初始化一個定位在sem的匿名信號量 |
sem_wait() | 把信號量減1操作,如果信號量的當前值為0則進入阻塞,為原子操作 |
sem_trywait() | 如果信號量的當前值為0則返回錯誤而不是阻塞調用(errno=EAGAIN),其實是sem_wait()的非阻塞版本 |
sem_post() | 給信號量的值加1,它是一個“原子操作”,即同時對同一個信號量做加1,操作的兩個線程是不會沖突的 |
sem_getvalue(sval) | 把sem指向的信號量當前值放置在sval指向的整數上 |
sem_destory(sem) | 銷毀由sem指向的匿名信號量 |
線程屬性配置相關函數
函數 | 說明 |
---|---|
pthread_attr_init() | 初始化配置一個線程對象的屬性,需要用pthread_attr_destroy函數去除已有屬性 |
pthread_attr_setscope() | 設置線程屬性 |
pthread_attr_setschedparam() | 設置線程優先級 |
pthread_attr_getschedparam() | 獲取線程優先級 |
基本的線程建立運行pthread_create
/* thread.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 3 /*線程數*/ #define REPEAT_NUMBER 5 /*每個線程中的小任務數*/ #define DELAY_TIME_LEVELS 10.0 /*小任務之間的最大時間間隔*/ // void *thrd_func(void *arg) { /* 線程函數例程 */ int thrd_num = (int)arg; int delay_time = 0; int count = 0; printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); for (no = 0; no < THREAD_NUMBER; no++) { /* 創建多線程 */ res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) { /* 等待線程結束 */ res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } } return 0; }
例程中循環3次建立3條線程,並且使用pthread_join函數依次等待線程結束;
線程中使用rand()獲取隨機值隨機休眠5次,隨意會出現后執行的線程先執行完成;
運行結果:
$ gcc thread.c -lpthread $ ./a.out Create treads success Waiting for threads to finish... Thread 0 is starting Thread 1 is starting Thread 2 is starting Thread 1: job 0 delay = 2 Thread 1: job 1 delay = 2 Thread 0: job 0 delay = 8 Thread 2: job 0 delay = 10 Thread 2: job 1 delay = 3 Thread 1: job 2 delay = 10 Thread 0: job 1 delay = 8 Thread 0: job 2 delay = 3 Thread 0: job 3 delay = 1 Thread 2: job 2 delay = 8 Thread 1: job 3 delay = 8 Thread 1: job 4 delay = 1 Thread 1 finished Thread 2: job 3 delay = 6 Thread 0: job 4 delay = 7 Thread 0 finished Thread 0 joined Thread 1 joined Thread 2: job 4 delay = 10 Thread 2 finished Thread 2 joined
可以看到,線程1先於線程0執行,但是pthread_join的調用時間順序,先等待線程0執行;
由於線程1已經早結束,所以線程0被pthread_join等到的時候,線程1已結束,就在等待到線程1時,直接返回;
線程執行的互斥和同步pthread_mutex_lock
在上面的程序中增加互斥鎖
/*thread_mutex.c*/ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 3 /* 線程數 */ #define REPEAT_NUMBER 3 /* 每個線程的小任務數 */ #define DELAY_TIME_LEVELS 10.0 /*小任務之間的最大時間間隔*/ pthread_mutex_t mutex; void *thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0, count = 0; int res; /* 互斥鎖上鎖 */ res = pthread_mutex_lock(&mutex); if (res) { printf("Thread %d lock failed\n", thrd_num); pthread_exit(NULL); } printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); /* 互斥鎖初始化 */ pthread_mutex_init(&mutex, NULL); for (no = 0; no < THREAD_NUMBER; no++) { res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); for (no = 0; no < THREAD_NUMBER; no++) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } } /****互斥鎖解鎖***/ pthread_mutex_unlock(&mutex); pthread_mutex_destroy(&mutex); return 0; }
在上面的例程中直接添加同步鎖pthread_mutex_t;
在線程中加入,於是程序在執行線程程序時;
調用pthread_mutex_lock上鎖,發現上鎖時候后進入等待,等待鎖再次釋放后重新上鎖;
所以線程程序加載到隊列中等待,等待成功上鎖后繼續執行程序代碼;
運行結果
$gcc thread_mutex.c -lpthread $ ./a.out Create treads success Waiting for threads to finish... Thread 0 is starting Thread 0: job 0 delay = 9 Thread 0: job 1 delay = 4 Thread 0: job 2 delay = 7 Thread 0 finished Thread 0 joined Thread 1 is starting Thread 1: job 0 delay = 6 Thread 1: job 1 delay = 4 Thread 1: job 2 delay = 7 Thread 1 finished Thread 1 joined Thread 2 is starting Thread 2: job 0 delay = 3 Thread 2: job 1 delay = 1 Thread 2: job 2 delay = 6 Thread 2 finished Thread 2 joined
跟例程1中執行結果不同,線程程序被加載到隊列中而不能馬上執行,需要等到能夠成功上鎖;
上鎖后,繼續執行線程程序,上鎖執行;
這樣線程被依次執行的情況在實際使用場景中經常出現;
使用場景:
當用戶登錄后獲取秘鑰才能繼續獲取該用戶的基本信息時;需要等待登錄線程結束后才能繼續執行獲取用戶信息的線程時,
需要調用兩條線程,假如是:threadLogin(),threadGetInfo(); 則可以有2種方法實現:
1 此時可以使用互斥鎖同時一次性調用完threadLogin()和threadGetInfo();
2 當然也可以不使用互斥鎖直接在threadLogin()中登錄驗證成功后調用threadGetInfo();
相比之下,方式1更加清晰的顯示邏輯關系,增加代碼可讀性可擴展性。
使用信號量控制線程的執行順序sem_post
修改上面例程,上面的是使用pthread_mutex_lock互斥鎖控制線程執行順序,
使用另外一種線程執行順序的控制;
/* thread_sem.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #define THREAD_NUMBER 3 #define REPEAT_NUMBER 3 #define DELAY_TIME_LEVELS 10.0 sem_t sem[THREAD_NUMBER]; void * thrd_func(void *arg) { int thrd_num = (int)arg; int delay_time = 0; int count = 0; sem_wait(&sem[thrd_num]); printf("Thread %d is starting\n", thrd_num); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread %d: job %d delay = %d\n", thrd_num, count, delay_time); } printf("Thread %d finished\n", thrd_num); pthread_exit(NULL); } int main(void) { pthread_t thread[THREAD_NUMBER]; int no = 0, res; void * thrd_ret; srand(time(NULL)); for (no = 0; no < THREAD_NUMBER; no++) { sem_init(&sem[no], 0, 0); res = pthread_create(&thread[no], NULL, thrd_func, (void*)no); if (res != 0) { printf("Create thread %d failed\n", no); exit(res); } } printf("Create treads success\n Waiting for threads to finish...\n"); sem_post(&sem[THREAD_NUMBER - 1]); for (no = THREAD_NUMBER - 1; no >= 0; no--) { res = pthread_join(thread[no], &thrd_ret); if (!res) { printf("Thread %d joined\n", no); } else { printf("Thread %d join failed\n", no); } sem_post(&sem[(no + THREAD_NUMBER - 1) % THREAD_NUMBER]); } for (no = 0; no < THREAD_NUMBER; no++) { sem_destroy(&sem[no]); } return 0; }
執行結果,仍然是建立3條線程,每條線程執行時休眠隨機時長:
$ gcc thread_sem.c -lpthread $ ./a.out Create treads success Waiting for threads to finish... Thread 2 is starting Thread 2: job 0 delay = 9 Thread 2: job 1 delay = 9 Thread 2: job 2 delay = 5 Thread 2 finished Thread 2 joined Thread 1 is starting Thread 1: job 0 delay = 5 Thread 1: job 1 delay = 7 Thread 1: job 2 delay = 4 Thread 1 finished Thread 1 joined Thread 0 is starting Thread 0: job 0 delay = 3 Thread 0: job 1 delay = 9 Thread 0: job 2 delay = 8 Thread 0 finished Thread 0 joined
執行結果與第2個例程非常相似,只不過教材中進行倒序執行而已;
那么這種方式其實與使用互斥鎖相比,代碼量可讀性基本持平不相上下;
線程的基本屬性pthread_attr_setscope
設置屬性一般有:
1 綁定屬性
2 分離屬性
3 堆棧地址
4 堆棧大小
5 優先級
關於綁定屬性就是綁定於內核線程;
分離屬性主要是講線程結束后是否馬上釋放相應的內存;
/* thread_attr.c */ #include <stdio.h> #include <stdlib.h> #include <pthread.h> #define THREAD_NUMBER 1 #define REPEAT_NUMBER 3 #define DELAY_TIME_LEVELS 10.0 int finish_flag = 0; void * thrd_func(void * arg){ int delay_time = 0; int count = 0; printf("Thread is starting\n"); for (count = 0; count < REPEAT_NUMBER; count++) { delay_time = (int)(rand() * DELAY_TIME_LEVELS/(RAND_MAX)) + 1; sleep(delay_time); printf("\tThread : job %d delay = %d\n", count, delay_time); } printf("Thread finished\n"); finish_flag = 1; pthread_exit(NULL); } int main(void) { pthread_t thread; pthread_attr_t attr; int res = 0; srand(time(NULL)); res = pthread_attr_init(&attr); if (res != 0) { printf("Create attribute failed\n"); exit(res); } res = pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM); res += pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (res != 0) { printf("Setting attribute failed\n"); exit(res); } res = pthread_create(&thread, &attr, thrd_func, NULL); if (res != 0) { printf("Create thread failed\n"); exit(res); } pthread_attr_destroy(&attr); printf("Create tread success\n"); while(!finish_flag){ printf("Waiting for thread to finish...\n"); sleep(2); } return 0; }
在運行前后使用 $ free 命令查看內存前后的使用情況發現:
在線程結束后內存馬上被釋放;
其實,一般線程的屬性直接使用系統默認屬性即可;
關於線程的使用,大約就是這樣。