C/C++ 多執行緒機制
一、C/C++多執行緒操作說明
C/C++多執行緒基本操作如下:
1. 執行緒的建立結束
2. 執行緒的互斥和同步
3. 使用訊號量控制執行緒
4. 執行緒的基本屬性配置
在C/C++程式碼編寫時,使用多執行緒機制,首先需要做的事情就是宣告引用,具體如下:
#include "pthread.h"
二、執行緒基本操作方法
基本執行緒操作:
1. pthread_create():建立執行緒開始執行相關執行緒函式,執行結束則執行緒退出
2. pthread_eixt():因為exit()是用來結束程序的,所以則需要使用特定結束執行緒的函式
3. pthread_join():掛起當前執行緒,用於阻塞式地等待執行緒結束,如果執行緒已結束則立即返回,0=成功
4. pthread_cancel():傳送終止訊號給thread執行緒,成功返回0,但是成功並不意味著thread會終止
5. pthread_testcancel():在不包含取消點,但是又需要取消點的地方建立一個取消點,以便在一個沒有包含取消點的執行程式碼執行緒中響應取消請求.
6. pthread_setcancelstate():設定本執行緒對cancle執行緒的反應
7. pthread_setcanceltype():設定取消狀態 繼續執行至下一個取消點再退出或者是立即執行取消動作
8. pthread_setcancel():設定取消狀態
三、執行緒互斥與同步機制
基本的互斥與同步的操作方法:
1. pthread_mutex_init():互斥鎖的初始化
2. pthread_mutex_lock():鎖定互斥鎖,如果嘗試鎖定已經被上鎖的互斥鎖則阻塞至可用為止
3. pthread_mutex_trylock():非阻塞的鎖定互斥鎖
4. pthread_mutex_unlock():釋放互斥鎖
5. pthread_mutex_destory():互斥鎖銷燬函式
四、訊號量執行緒控制機制
C/C++在使用訊號量機制的時候,預設的訊號量為匿名訊號量。
1. sem_init(sem):初始化一個定位在sem的匿名訊號量
2. sem_wait():把訊號量減1操作,如果訊號量的當前值為0則進入阻塞,為原子操作
3. sem_trywait():如果訊號量的當前值為0則返回錯誤而不是阻塞呼叫(errno=EAGAIN),其實是sem_wait()的非阻塞版本
4. sem_post():給訊號量的值加1,它是一個“原子操作”,即同時對同一個訊號量做加1,操作的兩個執行緒是不會衝突的
5. sem_getvalue(sval):把sem指向的訊號量當前值放置在sval指向的整數上
6. sem_destory(sem):銷燬由sem指向的匿名訊號量
五、多執行緒實踐
1. 基本的執行緒及建立執行
下面的程式碼是C/C++開發的基本的執行緒的執行,使用的就是最基本的pthread.h:
/* 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時,直接返回;
2. 執行緒執行的互斥和同步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
3. 使用訊號量控制執行緒的執行順序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個例程非常相似,只不過教材中進行倒序執行而已;
那麼這種方式其實與使用互斥鎖相比,程式碼量可讀性基本持平不相上下;