天天看點

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

為什麼要有線程?

對于任何一個程序來講,都預設有一個主線程的。

線程是負責一行一行執行二進制指令

程序相當于一個項目,而線程就是為了完成項目需求,而建立的一個個開發任務。

相對于線程

1、建立程序占用資源太多

2、程序之間的通信需要資料在不同的記憶體空間傳來傳去,無法共享

普通線程的建立和運作過程

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

模拟下載下傳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作業系統: 08 多線程與互斥鎖、條件變量
一步一步學linux作業系統: 08 多線程與互斥鎖、條件變量

多線程的資料

線程通路的資料可細分成三類

一步一步學linux作業系統: 08 多線程與互斥鎖、條件變量
  • 第一類是線程棧上的本地資料

    比如函數執行過程中的局部變量

    棧的大小可以通過指令 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作業系統: 08 多線程與互斥鎖、條件變量

條件變量

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

互斥鎖用于同步線程對共享資料的通路,而條件變量用于線上程之間同步共享資料的值,給多個線程提供了一個會合的場所。

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

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

老闆分發任務給程式員的例子

#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 再傳回,程式繼續向下執行

運作結果

[[email protected] 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 !
[[email protected] thread]$ gcc boss_task.c -lpthread
[[email protected] 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作業系統: 08 多線程與互斥鎖、條件變量

參考資料:

趣談Linux作業系統(極客時間)連結:

http://gk.link/a/10iXZ

歡迎大家來一起交流學習

繼續閱讀