一步一步學linux操作系統: 08 多線程與互斥鎖、條件變量

爲什麼要有線程?

對於任何一個進程來講,都默認有一個主線程的。
線程是負責一行一行執行二進制指令
進程相當於一個項目,而線程就是爲了完成項目需求,而建立的一個個開發任務。
相對於線程
1、創建進程佔用資源太多
2、進程之間的通信需要數據在不同的內存空間傳來傳去,無法共享

普通線程的創建和運行過程

圖片來自極客時間趣談linux操作系統
模擬下載N個非常大的視頻,看線程的使用流程

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_OF_TASKS 5	//最大線程數目

// 線程函數
void *downloadfile(void *filename)	// 函數參數是 void 類型的指針,用於接收任何類型的參數
{
   printf("I am downloading the file %s!\n", (char *)filename);
   sleep(10);
   long downloadtime = rand()%100;	//模擬下載時間
   printf("I finish downloading the file within %d minutes!\n", downloadtime);
   pthread_exit((void *)downloadtime);	
	// pthread_exit 退出線程, 傳入參數轉換爲 (void *) 類型,是線程退出的返回值
}

int main(int argc, char *argv[])
{
   char files[NUM_OF_TASKS][20]={"file1.avi","file2.rmvb","file3.mp4","file4.wmv","file5.flv"};
   pthread_t threads[NUM_OF_TASKS];	// pthread_t 類型的線程對象數組
   int rc;
   int t;
   int downloadtime;

   pthread_attr_t thread_attr;	// 線程屬性 pthread_attr_t 聲明
   pthread_attr_init(&thread_attr); //pthread_attr_init 初始化這個屬性 
   pthread_attr_setdetachstate(&thread_attr,PTHREAD_CREATE_JOINABLE); // 設置屬性 PTHREAD_CREATE_JOINABL, 這樣可以使用pthread_join(tid,&retstat)
	// PTHREAD_CREATE_JOINABL  參見 https://stackoverflow.com/questions/11806793/what-is-the-usage-of-pthread-create-joinable-in-pthread
	// 線程屬性pthread_attr_t簡介 參加 https://blog.csdn.net/hudashi/article/details/7709413

   for(t=0;t<NUM_OF_TASKS;t++){
     printf("creating thread %d, please help me to download %s\n", t, files[t]);
	// pthread_create 創建線程
	// 一共有四個參數,第一個參數是線程對象,第二個參數是線程的屬性,第三個參數是線程運行函數,第四個參數是線程運行函數的參數。
     rc = pthread_create(&threads[t], &thread_attr, downloadfile, (void *)files[t]);
     if (rc){
       printf("ERROR; return code from pthread_create() is %d\n", rc);
       exit(-1);
     }
   }
	// 銷燬線程屬性
   pthread_attr_destroy(&thread_attr);

   for(t=0;t<NUM_OF_TASKS;t++){
	//等待這些子任務完成  線程的返回值通過 pthread_join 傳給主線程
     pthread_join(threads[t],(void**)&downloadtime);
     printf("Thread %d downloads the file %s in %d minutes.\n",t,files[t],downloadtime);
   }

   pthread_exit(NULL);	// pthread_exit 退出主線程
}

gcc download.c -lpthread
在這裏插入圖片描述
在這裏插入圖片描述

多線程的數據

線程訪問的數據可細分成三類

圖片來自極客時間趣談linux操作系統

  • 第一類是線程棧上的本地數據
    比如函數執行過程中的局部變量
    棧的大小可以通過命令 ulimit -a 查看,默認情況下線程棧大小爲 8192(8MB)。可以使用命令 ulimit -s 修改。
    對於線程棧,可以通過函數 pthread_attr_t,修改線程棧的大小。
    int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
  • 第二類數據就是在整個進程裏共享的全局數據
    例如全局變量,雖然在不同進程中是隔離的,但是在一個進程中是共享的。
    同一個全局變量,兩個線程一起修改會出問題,需要一種機制來保護
  • 第三類數據,線程私有數據(Thread Specific Data)
    通過以下函數創建
    int pthread_key_create(pthread_key_t *key, void (destructor)(void))
    創建一個 key,伴隨着一個析構函數
    key 一旦被創建,所有線程都可以訪問它,但各線程可根據自己的需要往 key 中填入不同的值,這就相當於提供了一個同名而不同值的全局變量
    通過下面的函數設置 key 對應的 value
    int pthread_setspecific(pthread_key_t key, const void *value)
    可以通過下面的函數獲取 key 對應的 value
    void *pthread_getspecific(pthread_key_t key)
    等到線程退出的時候,就會調用析構函數釋放 value

數據的保護

Mutex互斥鎖

模式就是在共享數據訪問的時候,去申請加把鎖,誰先拿到鎖,誰就拿到了訪問權限

  • pthread_mutex_t g_money_lock; 聲明 名爲 g_money_lock的鎖
  • 使用 pthread_mutex_init 函數初始化這個 mutex
    pthread_mutex_init(&g_money_lock, NULL);
  • 使用pthread_mutex_lock() 就是去搶那把鎖的函數
    pthread_mutex_lock(&g_money_lock);
  • 使用pthread_mutex_unlock 釋放鎖
    pthread_mutex_unlock(&g_money_lock);
  • 調用 pthread_mutex_destroy 銷燬掉這把鎖
    pthread_mutex_destroy(&g_money_lock);

圖片來自極客時間趣談linux操作系統

條件變量

使用 pthread_mutex_lock(),需要一直在那裏等着。如果是 pthread_mutex_trylock() 不用一直阻塞,如果搶到了,就可以執行下一行程序,對共享變量進行訪問;如果沒搶到,不會被阻塞,而是返回一個錯誤碼。

互斥鎖用於同步線程對共享數據的訪問,而條件變量用於在線程之間同步共享數據的值,給多個線程提供了一個會合的場所。

條件變量本身是由互斥量保護的。線程在改變條件狀態之前必須先鎖住。

圖片來自極客時間趣談linux操作系統
老闆分發任務給程序員的例子


#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>

#define NUM_OF_TASKS 3
#define MAX_TASK_QUEUE 11

char tasklist[MAX_TASK_QUEUE]="ABCDEFGHIJ";	// 任務
int head = 0;	// 子線程任務接收偏移
int tail = 0;	// 主線程任務分發偏移

int quit = 0;

pthread_mutex_t g_task_lock;	//聲明鎖
pthread_cond_t g_task_cv;	//聲明條件變量

//程序員線程
void *coder(void *notused)
{
  pthread_t tid = pthread_self(); //獲取 線程id

  while(!quit){	

    pthread_mutex_lock(&g_task_lock);	// 加鎖  在調用pthread_coud_wait之前必須先鎖住互斥
    while(tail == head){
      if(quit){
        pthread_mutex_unlock(&g_task_lock);
        pthread_exit((void *)0);
      }
      printf("No task now! Thread %u is waiting!\n", (unsigned int)tid);
      pthread_cond_wait(&g_task_cv, &g_task_lock);	// 等待 條件變量通知
	/*
	注意:pthread_cond_wait函數會做3件事情
		第一: 把調用它的線程放到等待條件的線程列表上,(即通過條件變量傳遞消息的線程列表,可能多個線程同時等待)
		第二: 對互斥量解鎖,然後 線程就會阻塞在這裏直到通過條件變量傳過來的信號喚醒:pthread_coud_signal or pthread_coud_broadcast
		第三: 被喚醒後pthread_cond_wait() 將重新鎖定 g_task_lock 再返回,程序繼續向下執行
	*/
      printf("Have task now! Thread %u is grabing the task !\n", (unsigned int)tid);
    }
    char task = tasklist[head++];
    pthread_mutex_unlock(&g_task_lock);		// 解鎖 pthread_cond_wait 被喚醒後 重新鎖定 的 互斥量 g_task_lock
    printf("Thread %u has a task %c now!\n", (unsigned int)tid, task);
    sleep(5);
    printf("Thread %u finish the task %c!\n", (unsigned int)tid, task);
  }

  pthread_exit((void *)0);
}

int main(int argc, char *argv[])
{
  pthread_t threads[NUM_OF_TASKS];
  int rc;
  int t;

  pthread_mutex_init(&g_task_lock, NULL);	//初始化 鎖
  pthread_cond_init(&g_task_cv, NULL);	//初始化 條件變量

  for(t=0;t<NUM_OF_TASKS;t++){
    rc = pthread_create(&threads[t], NULL, coder, NULL);	//創建線程
    if (rc){
      printf("ERROR; return code from pthread_create() is %d\n", rc);
      exit(-1);
    }
  }

  sleep(5);

  for(t=1;t<=4;t++){
    pthread_mutex_lock(&g_task_lock);	// 加鎖 線程在改變條件狀態之前必須先鎖住
    tail+=t;	// 任務 偏移 +t
    printf("I am Boss, I assigned %d tasks, I notify all coders!\n", t);
    pthread_cond_broadcast(&g_task_cv);	// 通知喚醒 等待條件g_task_cv 的所有線程 
    pthread_mutex_unlock(&g_task_lock);	// 解鎖
    sleep(20);
  }

  pthread_mutex_lock(&g_task_lock);	// 加鎖
  quit = 1;	// 用於推出循環
  pthread_cond_broadcast(&g_task_cv);// 通知喚醒 等待條件g_task_cv 的所有線程 
  pthread_mutex_unlock(&g_task_lock);// 解鎖

  pthread_mutex_destroy(&g_task_lock); //銷燬鎖
  pthread_cond_destroy(&g_task_cv);	// 銷燬共享變量
  pthread_exit(NULL);
}

注意:pthread_cond_wait函數會做3件事情
第一: 把調用它的線程放到等待條件的線程列表上,(即通過條件變量傳遞消息的線程列表,可能多個線程同時等待)
第二: 對互斥量解鎖,然後 線程就會阻塞在這裏直到通過條件變量傳過來的信號喚醒:pthread_coud_signal or pthread_coud_broadcast
第三: 被喚醒後pthread_cond_wait() 將重新鎖定 g_task_lock 再返回,程序繼續向下執行

運行結果

[leacoder@localhost thread]$ ./a.out 
// pthread_cond_wait 會 對互斥量解鎖 故 三個線程均執行到這裏
No task now! Thread 882140928 is waiting!
No task now! Thread 873748224 is waiting!
No task now! Thread 865355520 is waiting!
// 分發 1個任務 A  tail+=1   while(tail == head) 爲false 差 1
I am Boss, I assigned 1 tasks, I notify all coders!
Have task now! Thread 882140928 is grabing the task !
// 882140928  條件滿足,出循環向下執行 head++,這時 while(tail == head) 爲true
Thread 882140928 has a task A now! // 執行任務 A
Have task now! Thread 873748224 is grabing the task !
// 882140928   條件滿足,可while(tail == head) 爲true  循環繼續等待
No task now! Thread 873748224 is waiting!

Have task now! Thread 865355520 is grabing the task !
// 865355520 條件滿足,可while(tail == head) 爲true  循環繼續等待
No task now! Thread 865355520 is waiting!

Thread 882140928 finish the task A!//結束任務 A
No task now! Thread 882140928 is waiting! // 882140928  繼續等待

// 分發 2個任務 B C  tail+=2   while(tail == head) 爲false 差 2
I am Boss, I assigned 2 tasks, I notify all coders!

Have task now! Thread 873748224 is grabing the task !
// 873748224 條件滿足,出循環向下執行 head++,這時 while(tail == head) 爲false 差 1
Thread 873748224 has a task B now!// 執行任務 B

Have task now! Thread 865355520 is grabing the task !
// 865355520 條件滿足,出循環向下執行 head++B,這時 while(tail == head) 爲true
Thread 865355520 has a task C now!// 執行任務 C

Have task now! Thread 882140928 is grabing the task !
// 882140928 條件滿足,可while(tail == head) 爲true  循環繼續等待
No task now! Thread 882140928 is waiting!

Thread 873748224 finish the task B!//結束任務 B
No task now! Thread 873748224 is waiting!// 873748224 繼續等待

Thread 865355520 finish the task C!//結束任務 C
No task now! Thread 865355520 is waiting!// 865355520 繼續等待


I am Boss, I assigned 3 tasks, I notify all coders!
Have task now! Thread 882140928 is grabing the task !
Thread 882140928 has a task D now!
Have task now! Thread 873748224 is grabing the task !
Thread 873748224 has a task E now!
Have task now! Thread 865355520 is grabing the task !
Thread 865355520 has a task F now!
Thread 882140928 finish the task D!
Thread 865355520 finish the task F!
Thread 873748224 finish the task E!
No task now! Thread 882140928 is waiting!
No task now! Thread 865355520 is waiting!
No task now! Thread 873748224 is waiting!
I am Boss, I assigned 4 tasks, I notify all coders!
Have task now! Thread 882140928 is grabing the task !
Thread 882140928 has a task G now!
Have task now! Thread 865355520 is grabing the task !
Thread 865355520 has a task H now!
Have task now! Thread 873748224 is grabing the task !
Thread 873748224 has a task I now!
Thread 873748224 finish the task I!
Thread 873748224 has a task J now!
Thread 882140928 finish the task G!
No task now! Thread 882140928 is waiting!
Thread 865355520 finish the task H!
No task now! Thread 865355520 is waiting!
Thread 873748224 finish the task J!
No task now! Thread 873748224 is waiting!
Have task now! Thread 865355520 is grabing the task !
Have task now! Thread 873748224 is grabing the task !
Have task now! Thread 882140928 is grabing the task !
[leacoder@localhost thread]$ gcc boss_task.c -lpthread
[leacoder@localhost thread]$ ./a.out 
No task now! Thread 2711508736 is waiting!
No task now! Thread 2719901440 is waiting!
No task now! Thread 2728294144 is waiting!
I am Boss, I assigned 1 tasks, I notify all coders!
Have task now! Thread 2711508736 is grabing the task !
Thread 2711508736 has a task A now!
Have task now! Thread 2719901440 is grabing the task !
No task now! Thread 2719901440 is waiting!
Have task now! Thread 2728294144 is grabing the task !
No task now! Thread 2728294144 is waiting!
Thread 2711508736 finish the task A!
No task now! Thread 2711508736 is waiting!
I am Boss, I assigned 2 tasks, I notify all coders!
Have task now! Thread 2719901440 is grabing the task !
Thread 2719901440 has a task B now!
Have task now! Thread 2728294144 is grabing the task !
Thread 2728294144 has a task C now!
Have task now! Thread 2711508736 is grabing the task !
No task now! Thread 2711508736 is waiting!
Thread 2719901440 finish the task B!
No task now! Thread 2719901440 is waiting!
Thread 2728294144 finish the task C!
No task now! Thread 2728294144 is waiting!
I am Boss, I assigned 3 tasks, I notify all coders!
Have task now! Thread 2711508736 is grabing the task !
Thread 2711508736 has a task D now!
Have task now! Thread 2719901440 is grabing the task !
Thread 2719901440 has a task E now!
Have task now! Thread 2728294144 is grabing the task !
Thread 2728294144 has a task F now!
Thread 2711508736 finish the task D!
No task now! Thread 2711508736 is waiting!
Thread 2728294144 finish the task F!
No task now! Thread 2728294144 is waiting!
Thread 2719901440 finish the task E!
No task now! Thread 2719901440 is waiting!
I am Boss, I assigned 4 tasks, I notify all coders!
Have task now! Thread 2711508736 is grabing the task !
Thread 2711508736 has a task G now!
Have task now! Thread 2728294144 is grabing the task !
Thread 2728294144 has a task H now!
Have task now! Thread 2719901440 is grabing the task !
Thread 2719901440 has a task I now!
Thread 2711508736 finish the task G!
Thread 2711508736 has a task J now!
Thread 2719901440 finish the task I!
No task now! Thread 2719901440 is waiting!
Thread 2728294144 finish the task H!
No task now! Thread 2728294144 is waiting!
Thread 2711508736 finish the task J!
No task now! Thread 2711508736 is waiting!
Have task now! Thread 2728294144 is grabing the task !
Have task now! Thread 2719901440 is grabing the task !
Have task now! Thread 2711508736 is grabing the task !

總結圖

圖片來自極客時間趣談linux操作系統

參考資料:

趣談Linux操作系統(極客時間)鏈接:
http://gk.link/a/10iXZ
歡迎大家來一起交流學習

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章