1. 程式人生 > >C/C++ 多執行緒機制

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個例程非常相似,只不過教材中進行倒序執行而已; 
那麼這種方式其實與使用互斥鎖相比,程式碼量可讀性基本持平不相上下;