1. 程式人生 > >linux 程序和執行緒 對比

linux 程序和執行緒 對比

執行緒和程序

這個概念不管在什麼作業系統中都是一樣的,也是面試官比較喜歡問的,代表你對程式優化的功底,搞安卓的時候,經常被用來優化處理速度 還有資料的處理,結合handler 一起處理,解決經常出現介面死掉問題。

既然總結了,這一次充分總結下:

程序

程序組成:

  1. 程序控制塊PCB
  2. 程式段
  3. 資料段

程序控制塊PCB 是核心中存放的一塊PCB區域,記錄型資料結構 ,PCB 記錄了作業系統所需要的引數,用來描述程序目前情況和程序執行的全部資訊,包括

  1. 程序描述資訊:
    1. 程序識別符號,識別程序
    2. 使用者標識,用於資源共享和保護
    3. 家族關係,程序關於有父程序和子程序的資訊
  2. 處理機狀態資訊:包含通用暫存器、指令暫存器、程式狀態字(PSW)、使用者棧指標等
    1. 程序狀態 ,用作程序排程的依據
    2. 程序的優先順序,處理機執行程序的依據
    3. 程序排程需要的資料: 比如已經等待CPU的總和、程序已經執行的時間總和
    4. 事件: 程序被阻塞的原因
  3. 程序控制資訊
    1. 程式和資料的地址
    2. 程序和同步通訊機制

程序的狀態: 就緒、執行、阻塞

process

上面是程序的各種狀態切換

當第一初始化程序的時候,程序進入就緒狀態,等待OS 分配處理機處理程序,當程序獲取處理機處理,程序執行階段,執行的時候,一般會用來處理各種事物,比如 請求 IO 處理等,但是一旦有大量IO 處理,容易進入阻塞狀態,此時就是我們經常看見電腦卡死的狀態,但是處理完後,程序會進入就緒狀態
其實還有一種狀態,finish 結束狀態,但是隻要程序死了就直接退出了

執行緒

從上面很容易看出來,建立一個程序,因為直接和linux 核心進行處理資料,需要提供大量的引數,來保障程序的安全,高效,穩定,消耗很多系統資源,但是執行緒就不一樣,可以原理是一樣的,作為輕量級,消耗系統資源很少,而且作為開發者來說,執行緒用起來更爽,更方便,執行緒只能跑在程序中,所以,和系統核心互動的資料都交給程序了,執行緒簡單方便易用,據說程序消耗資源是執行緒的30倍…..
執行緒和處理流程和 程序差不錯 ,也是 就緒、執行、阻塞、結束四個狀態

多執行緒程式設計的API

建立執行緒

int pthread_create(pthread_t *thread,pthread_attr_t *attr,void
* (*func)(void * ),void *arg); /* 看了上面的方法,mmp、wtf 這些詞彙從我腦海中冒出來,函式引數要不要寫的那麼複雜 只能看呼叫示例了: pthread(&thrd1,NULL,(void*)task1,(void*)&g1); phtread thrd1 表示建立執行緒的標識 pthread_attr_t *attr 表示執行緒屬性NULL 預設 void task1 執行緒需要執行的程式碼 int g1 表示task1的引數 */

結束執行緒

pthread_exit(void *retval);
//retval 用來存放自殺執行緒退出狀態

等待執行緒結束

// 多個執行緒啟動後,由系統負責排程,但我們並不知道哪個會先開始和結束,只能等待
int pthread_join(pthread_t th,void **thread_return);
/*
th 等待執行緒的標識
thread_return 返回的狀態
*/

多執行緒例項:

只有同步鎖的情況,執行緒之間的同步互斥

用同步互斥原理,實現對變數shareid的操作

int shareid=0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int main(int argc, char const *argv[]) {
  pthread_t thrd1,thrd2;
  int ret;
  ret = pthread_create(&thrd1,NULL,(void *)task1,NULL);//建立執行緒
  ret = pthread_create(&thrd2,NULL,(void *)task2,NULL);
  pthread_join(thrd1,NULL);//等待執行緒結束
  pthread_join(thrd2,NULL);
  printf("shareid = %d\n",shareid );
  return 0;
}
  void task1(void) {
    long i,tmp;
    for ( i = 0; i < 10000; i++) {
      if (pthread_mutex_lock(&mutex)!=0) {//加同步鎖
        perror("pthread_mutex_lock");
          exit(EXIT_FAILURE);
      }
      tmp = shareid;
      tmp = tmp+1;
      shareid = tmp;
      if (pthread_mutex_unlock(&mutex)!=0) {//解鎖
        perror("pthread_mutex_unlock");
        exit(EXIT_FAILURE);
      }
    }
    printf("task1 finished shareid = %d\n" ,shareid);
  }
  void task2(void) {
    long i,tmp;
    for ( i = 0; i < 5000; i++) {
      if (pthread_mutex_lock(&mutex)!=0) {
        perror("pthread_mutex_lock");
          exit(EXIT_FAILURE);
      }
      tmp = shareid;
      tmp = tmp+1;
      shareid = tmp;
      if (pthread_mutex_unlock(&mutex)!=0) {
        perror("pthread_mutex_unlock");
        exit(EXIT_FAILURE);
      }
    }
    printf("task2 finished shareid = %d\n" ,shareid);
  }

因為 pthread 不是gcc 編譯標準庫,需要用到動態庫載入,編譯編譯命令最後 加上-lpthread

gcc pthread_create.c -o pthread_create -lpthread

debug 結果:

ph_create

執行緒+同步鎖+訊號量 會有更有意思

很明顯同步鎖,有點死板,每次只能操作一個變數,不能實現多個公共資源的操作,但是加上訊號量就有意思了

semaphore API 介紹

建立訊號量

#include <semaphore.h>
int sem_init(sem_t *sem,int pshared,unsigned int value);
/*
功能: 初始化 訊號量
返回值: 成功返回0 錯誤返回-1
sem: 指向訊號量結構的指標
pshared 不為0 訊號量在程序間共享,不然只能在本程序中的所有執行緒中共享
value 給出訊號量的初始值
*/

訊號量 P操作 -1

int sem_wait(sem_t *sem);

訊號量 V操作 +1

int sem_post(sem_t *sem);

訊號量刪除

int sem_destroy(sem_t *sem);

看了上面的API,和程序中用的API 不一樣? linux ipc 程序間通訊總結
我只能這麼理解了,程序和執行緒適用不同的訊號量API

案例如下:

# define MAXSIZE 10

int stack[MAXSIZE][2];
int size = 0;
sem_t sem;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void provide_data(void){

  int i;
  for (i = 0; i < MAXSIZE; i++) {
    stack[i][0] = i;
    stack[i][1] = i;
    sem_post(&sem);// 訊號量 V操作 +1
  }
}


void handle_data1(void) {
  int i;
  while (pthread_mutex_lock(&mutex),((i =size++)<MAXSIZE)) {
    pthread_mutex_unlock(&mutex);
    sem_wait(&sem);
    printf("Plus : %d + %d =%d \n",stack[i][0],stack[i][1],stack[i][0]+stack[i][1] );
  }
  pthread_mutex_unlock(&mutex);
}

void handle_data2(void) {
  int i;
  while (pthread_mutex_lock(&mutex),((i =size++)<MAXSIZE)) {
    pthread_mutex_unlock(&mutex);
    sem_wait(&sem);
    printf("Multiple : %d × %d =%d \n",stack[i][0],stack[i][1],stack[i][0]*stack[i][1] );
  }
  pthread_mutex_unlock(&mutex);
}

int main(int argc, char const *argv[]) {
  pthread_t thd1,thd2,thd3;
  sem_init(&sem,0,0);
  pthread_create(&thd1,NULL,(void *)handle_data1,NULL);
  pthread_create(&thd2,NULL,(void *)handle_data2,NULL);
  pthread_create(&thd3,NULL,(void *)provide_data,NULL);

  pthread_join(thd1,NULL);
  pthread_join(thd2,NULL);
  pthread_join(thd3,NULL);
  sem_destroy(&sem);
  return 0;
}

有一個執行緒th3對 stack 迴圈賦值,並且每賦值一次 將訊號量sem V操作,thd1和thd2這兩個執行緒會競爭獲取stack 操作 thd1 是求和,而thd2是求商,哪一個執行緒獲取資源了,求和或者求商,然後對sem程序P操作,所以每次執行程式會列印不同的結果
這裡寫圖片描述
這裡還有一個知識點, 逗號運算子

  while (pthread_mutex_lock(&mutex),((i =size++)<MAXSIZE)) {
    pthread_mutex_unlock(&mutex);
/*
 while 有兩個表示式,最後只判斷((i =size++) <MAXSIZE) 是否符合,前面只是對mutex 進行加鎖操作,這樣只是為了保證size 能正常執行++操作和判斷
 */

逗號表示式: 逗號前面和後面的表示式都執行,但是隻有最後一個返回值有效

其他unbelieve知識點

pthread_create 上面第二個引數一直設定NULL 系統預設屬性,但是如果我們設定,該如何設定呢?

如果設定執行緒屬性,必須在pthread_create 之前使用pthread_attr_init.

pthread_attr_t 結構體包含 是否繫結,是否分離,堆疊地址,堆疊大小,優先順序資訊

預設屬性是 非繫結 非分離 預設1M大小堆疊 優先順序和程序一樣

輕程序概念,理解為核心程序,位於使用者層和系統層之間,系統對執行緒資源的分配通過輕程序實現,如果設定繫結在輕程序,那個執行緒響應度高。

繫結狀態

設定繫結狀態的函式 pthread_attr_setscope

繫結:PTHREAD_SCOPE_SYSTEM 非繫結 PTHREAD_SCOPE_PROCESS

分離狀態

執行緒的分離狀態,用來決定如何結束自己
非分離狀態使用pthread_join 處理,才能釋放資源,分離狀態的執行緒,執行結束,自動釋放

非分離:PTHREAD_CREATE_JOINABLE 分離:PTHREAD_CREATE_DETACHED 設定引數的函式 pthread_attr_setdetachstate

如果設定執行緒分離狀態,執行緒執行太快,在pthread_attr_init 之後,但是在pthread_create之前結束,那麼此時建立執行緒,會得到錯誤執行緒號,需要避免,最簡單的方法 執行pthread_cond_timewait 使得執行緒慢點跑

執行緒優先順序

執行緒優先順序 存放在結構體sched_param 用pthread_attr_getsparam 函式 和pthread_attr_setschedpara 存放, 先取得優先順序,修改後,存放回去

執行緒建立後改變屬性:

殺死其他執行緒的方法 pthread_cancel(thread)

但是執行緒可以設定屬性 拒絕被殺死

pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,NULL)
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL)

執行緒真的很不錯的,優化用的特別多