12.5 同步
如何實現不一樣多線程中的切換?如何保護那些共享的變量?
一、信號量機制:使用信號量實現同步
兩組函數接口用於信號量:1)、取自POSIX的實時擴展,用於線程;2)、系統V信號量,用於進程的同步。
荷蘭計算機科學家Dijkstra首先提出信號量的概念,信號量是一個特殊類型的變量,他能夠被增長或者減小,但對其的關鍵訪問被保證是原子操做,即便在一個多線程的程序中也是如此。則意味着若是一個程序中兩個或者多個線程視圖改變同一個信號量的值,系統將保證執行的順序都是依次執行。
1)、最簡單的信號量:二進制信號量
它只有0和1兩個取值。
2)、計數型信號量:
他能夠有更大範圍的取值。
信號量通常用來保護一段代碼,使其每次只能被一個線程訪問,此時須要二進制信號量。有時但願容許有限的線程同時訪問執行一段代碼,此時須要計數型信號量。
信號量函數的名字都以sem_開頭,線程中使用的基本信號量函數有4個:
程序員
#include <semaphore.h> int sem_init(sem_t *sem, int pshared,unsigned int value);
這個函數初始化sem指向的信號量的值,設置它的共享選項,並給他一個初始的整數值。pshared參數控制信號量的類型;若是它的值爲0,就表示這個信號量是當前進程的局部信號量,不然這個信號量就能夠在多個進程間共享。
數組
#include <semaphore.h> int sem_wait(sem_t * sem); int sem_post(sem_t* sem);
兩個函數都以一個指針爲參數,該指針指向的對象是由sem_init調用初始化的信號量。
sem_post函數的做用是以原子操做的方式給信號量的值加1。所謂原子操做是指,若是兩個線程企圖同時給一個信號量加1,它們之間不會互相干擾,而不像若是兩個程序同時對同一個文件進行讀取、增長、寫入操做時可能會引發衝突。信號量的值老是會被正確地加2,由於有兩個線程試圖改變它。
sem_wait函數以原子操做的方式將信號量的值減1,但它會等待直到信號量有個非零值纔會開始減法操做。所以,若是對值爲2的信號量調用sem_wait,線程將繼續執行,但信號量的值會減到1。若是對值爲0的信號量調用sem_wait,這個函數就會等待,直到有其餘線程增長了該信號量的值使其再也不是0爲止。若是兩個線程同時在sem_wait調用上等待同一個信號量變爲非零值,那麼當該信號量被第三個線程增長1時,只有其中一個等待線程將開始對信號量減1,而後繼續執行,另一個線程還將繼續等待。
最後一個信號量函數是sem_destroy。這個函數的做用是,用完信號量後對它進行清理。它的定義以下:
多線程
#include <semaphore.h>
int sem_destory(sem_t * sem);
與前幾個函數同樣,這個函數也以一個信號量指針爲參數,並清理該信號量擁有的全部資源。若是企圖清理的信號量正被一些線程等待,就會收到一個錯誤。
實驗一:使用信號量進行同步:函數
1 #include <stdlib.h> 2 #include <stdio.h> 3 #include <unistd.h> 4 #include <semaphore.h> 5 #include <pthread.h> 6 #include <string.h> 7 void * thread_function(void* arg); 8 sem_t bin_sem; 9 #define WORK_SIZE 1024 10 char work_area[WORK_SIZE]; 11 int main(int argc, char const *argv[]) 12 { 13 int res; 14 pthread_t a_thread; 15 void* thread_result; 16 //初始化信號量 17 res=sem_init(&bin_sem,0,0); 18 if(res!=0) 19 { 20 perror("Semaphore init failed"); 21 exit(EXIT_FAILURE); 22 } 23 //建立一個線程 24 res=pthread_create(&a_thread,NULL,thread_function,NULL); 25 if(res!=0) 26 { 27 perror("Create thread failed"); 28 exit(EXIT_FAILURE); 29 } 30 printf("input some text. Enter 'end' to finish\n"); 31 while(strncmp(work_area,"end",3)!=0) 32 { 33 fgets(work_area,WORK_SIZE,stdin); 34 printf("建立者釋放一個信號量\n"); 35 sem_post(&bin_sem);//釋放一個信號量 36 } 37 printf("\nWaiting for thread to finish\n"); 38 res=pthread_join(a_thread,&thread_result); 39 if(res!=0) 40 { 41 perror("Thread join failed"); 42 exit(EXIT_FAILURE); 43 } 44 printf("Thread joined\n"); 45 sem_destroy(&bin_sem); 46 exit(EXIT_SUCCESS); 47 return 0; 48 } 49 void* thread_function(void* arg) 50 { 51 printf("線程申請信號量\n"); 52 sem_wait(&bin_sem); 53 printf("申請信號量成功\n"); 54 while(strncmp("end",work_area,3)!=0) 55 { 56 printf("You input %d :characters\n",(int)strlen(work_area)); 57 printf("線程申請信號量\n"); 58 sem_wait(&bin_sem); 59 printf("申請信號量成功\n"); 60 } 61 pthread_exit(NULL); 62 }
該程序有一個全局變量:work_area,用來接受用戶的輸入;建立一個線程用來輸出用戶輸入的字符串的長度。
建立線程後,線程執行thread_function,申請信號量,此時,在主程序中,用戶尚未書輸入數據,線程進入阻塞狀態,當用戶輸入數據後,主程序釋放一個信號量,此時線程成功的獲得了信號量,就能夠執行後面的代碼。
二、使用互斥量進行同步
互斥量容許程序員鎖住某個對象,使得每次只能有一個線程訪問。爲了控制對關鍵代碼的訪問,必須在進入這段代碼以前鎖住一個互斥量,而後在完成操做以後解鎖它。
函數定義: post
#include <pthread.h> int pthread_mutex_init(pthread_mutex_t * mutex,const pthread_mutexattr_t* mutexattr); int pthread_mutex_lock(pthread_mutex_t* mutex); int pthread_mutex_unlock(pthread_mutex_t* mutex); int pthread_mutex_destory(pthread_mutex_t* mutex);
與其餘函數同樣,成功時返回0,失敗時將返回錯誤代碼,但這些函數並不設置errno,你必須對函數的返回代碼進行檢查。
與信號量相似,這些函數的參數都是一個先前聲明過的對象的指針。對互斥量來講,這個對象的類型爲pthread_mutex_t。pthread_mutex_init函數中的屬性參數容許咱們設置互斥量的屬性,而屬性控制着互斥量的行爲。屬性類型默認爲fast,但它有一個小缺點:若是程序試圖對一個已經加了鎖的互斥量調用pthread_mutex_lock,程序就會被阻塞,而又由於擁有互斥量的這個線程正是如今被阻塞的線程,因此互斥量就永遠也不會被解鎖了,程序也就進入死鎖狀態。這個問題能夠經過改變互斥量的屬性來解決,咱們可讓它檢查這種狀況並返回一個錯誤,或者讓它遞歸的操做,給同一個線程加上多個鎖,但必須注意在後面執行同等數量的解鎖操做。
實驗二:spa
1 #include<stdio.h> 2 #include<stdlib.h> 3 #include<unistd.h> 4 #include<string.h> 5 #include<semaphore.h> 6 #include<pthread.h> 7 void* thread_function(void*arg); 8 pthread_mutex_t work_mutex; 9 #define WORK_SIZE 1024 10 char work_area[WORK_SIZE]; 11 int time_to_exit=0; 12 int main() 13 { 14 int res; 15 pthread_t a_thread; 16 void* thread_res; 17 res=pthread_mutex_init(&work_mutex,NULL);//初始化互斥量 18 if(res!=0) 19 { 20 perror("Init mutex failed"); 21 exit(EXIT_FAILURE); 22 } 23 //建立線程 24 res=pthread_create(&a_thread,NULL,thread_function,NULL); 25 if(res!=0) 26 { 27 perror("Create thread failed"); 28 exit(EXIT_FAILURE); 29 } 30 //鎖住互斥量 31 pthread_mutex_lock(&work_mutex); 32 printf("Input some text. Ente 'end' to finish\n"); 33 while(!time_to_exit) 34 { 35 fgets(work_area,WORK_SIZE,stdin); 36 pthread_mutex_unlock(&work_mutex);//解鎖互斥量 37 while(1) 38 { 39 pthread_mutex_lock(&work_mutex); 40 if(work_area[0]!='\0') 41 { 42 pthread_mutex_unlock(&work_mutex); 43 sleep(1); 44 } 45 else 46 break; 47 } 48 } 49 pthread_mutex_unlock(&work_mutex); 50 printf("\nWaiting for thread to finish...\n"); 51 res=pthread_join(a_thread,&thread_res); 52 if(res!=0) 53 { 54 perror("Thread join error"); 55 exit(EXIT_FAILURE); 56 } 57 printf("Thread joined\n"); 58 pthread_mutex_destroy(&work_mutex); 59 exit(EXIT_SUCCESS); 60 return 0; 61 } 62 void* thread_function(void* arg) 63 { 64 sleep(1); 65 pthread_mutex_lock(&work_mutex); 66 while(strncmp("end",work_area,3)!=0) 67 { 68 printf("You input %d characters\n",(int)strlen(work_area)-1); 69 work_area[0]='\0'; 70 pthread_mutex_unlock(&work_mutex); 71 sleep(1); 72 pthread_mutex_lock(&work_mutex); 73 while(work_area[0]=='\0') 74 { 75 pthread_mutex_unlock(&work_mutex); 76 sleep(1); 77 pthread_mutex_lock(&work_mutex); 78 } 79 } 80 time_to_exit=1; 81 work_area[0]='\0'; 82 pthread_mutex_unlock(&work_mutex); 83 pthread_exit(0); 84 }
新線程首先試圖對互斥量加鎖。若是它已經被鎖住,這個調用將被阻塞直到它被釋放爲止。一旦得到訪問權,咱們就檢查是否有申請退出程序的請求。若是有,就設置time_to_exit變量,再把工做區的第一個字符設置爲\0,而後退出。
若是不想退出,就統計字符個數,而後把work_area數組中的第一個字符設置爲null。咱們用將第一個字符設置爲null的方法通知讀取輸入的線程,咱們已完成了字符統計。而後解鎖互斥量並等待主線程繼續運行。咱們將週期性地嘗試給互斥量加鎖,若是加鎖成功,就檢查是否主線程又有字符送來要處理。若是尚未,就解鎖互斥量繼續等待;若是有,就統計字符個數並再次進入循環。
線程