1. 程式人生 > >學習整理——多程序和多執行緒概念理解

學習整理——多程序和多執行緒概念理解

程序

        一個程序,包括了程式碼、資料和分配給程序的資源(記憶體),在計算機系統裡直觀地說一個程序就是一個PID。作業系統保護程序空間不受外部程序干擾,即一個程序不能訪問到另一個程序的記憶體。有時候程序間需要進行通訊,這時可以使用作業系統提供程序間通訊機制。通常情況下,執行一個可執行檔案作業系統會為其建立一個程序以供它執行。但如果該執行檔案是基於多程序設計的話,作業系統會在最初的程序上創建出多個程序出來,這些程序間執行的程式碼是一樣,但執行結果可能是一樣的,也可能是不一樣的。

為什麼需要多程序?最直觀的想法是,如果作業系統支援多核的話,那麼一個執行檔案可以在不同的核心上跑;即使是非多核的,在一個程序在等待I/O操作時另一個程序也可以在CPU上跑,提高CPU利用率、程式的效率。

在Linux系統上可以通過fork()來在父程序中創建出子程序。一個程序呼叫fork()後,系統會先給新程序分配資源,例如儲存資料和程式碼空間。然後把原來程序的所有值、狀態都複製到新的程序裡,只有少數的值與原來的程序不同,以區分不同的程序。fork()函式會返回兩次,一次給父程序(返回子程序的pid或者fork失敗資訊),一次給子程序(返回0)。至此,兩個程序分道揚鑣,各自執行在系統裡。

#include <unistd.h>  
#include <sys/types.h> 
#include <sys/wait.h>     
#include <stdio.h>  
#include <stdlib.h>
 
void print_exit(){
    printf("the exit pid:%d\n",getpid() );     
}  
  
int main (){
    pid_t pid;   
    atexit( print_exit );      //註冊該程序退出時的回撥函式  
    pid=fork();   // new process

    int count = 0;

    if (pid < 0){
        printf("error in fork!");
    }   
    else if (pid == 0){
        printf("i am the child process, my process id is %d\n",getpid());
        count ++;
        printf("child process add count.\n");
    }
    else {  
        printf("i am the parent process, my process id is %d\n",getpid());

        count++; 
        printf("parent process add count.\n");  
        sleep(2);  
        wait(NULL);  
    }  

    printf("At last, count equals to %d\n", count);
   return 0;
} 
上述程式碼在fork()之後進行了一次判斷,以辨別當前程序是父程序還是子程序。

為了說明父程序與子程序有各自的資源空間,設定了對count的計數。Terminal輸出如下:、


明顯兩個程序都執行了count++操作,但由於count是分別處在不同的程序裡,所以實質上count在各自程序上只執行了一次。

執行緒

        執行緒是可執行程式碼的可分派單元,CPU可單獨執行單元。在基於執行緒的多工的環境中,所有程序至少有一個執行緒(主執行緒),但是它們可以具有多個任務。這意味著單個程式可以併發執行兩個或者多個任務。
 
也就是說,執行緒可以把一個程序分為很多片,每一片都可以是一個獨立的流程

,CPU可以選擇其中的流程來執行。但執行緒不是程序,不具有PID,且分配的資源屬於它的程序,共享著程序的全域性變數,也可以有自己“私有”空間。但這明顯不同於多程序,程序是一個拷貝的流程,而執行緒只是把一條河流截成很多條小溪。它沒有拷貝這些額外的開銷,但是僅僅是現存的一條河流,就被多執行緒技術幾乎無開銷地轉成很多條小流程,它的偉大就在於它少之又少的系統開銷。

Linux中可以使用pthread庫來建立執行緒,但由於pthread不是Linux核心的預設庫,所以編譯時需要加入pthread庫一同編譯。

g++ -o main main.cpp -pthread

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <unistd.h>  
#include <pthread.h>  
  
void* task1(void*);  
void* task2(void*);  

void usr();  
int p1,p2;  
 
int count = 0 ;

int main()  {  
    usr();  
    return 0;  
}  
  
void usr(){  
    pthread_t pid1, pid2;  
    pthread_attr_t attr;  
    void *p1, *p2;  
    int ret1=0, ret2=0;  
    pthread_attr_init(&attr);                                                  //初始化執行緒屬性結構  
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);   //設定attr結構 
    pthread_create(&pid1, &attr, task1, NULL);            //建立執行緒,返回執行緒號給pid1,執行緒屬性設定為attr的屬性 
    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);  
    pthread_create(&pid2, &attr, task2, NULL);            
  
    ret1=pthread_join(pid1, &p1);         //等待pid1返回,返回值賦給p1  
    ret2=pthread_join(pid2, &p2);         //等待pid2返回,返回值賦給p2
    printf("after pthread1:ret1=%d,p1=%d\n", ret1,(int)p1);    
    printf("after pthread2:ret2=%d,p2=%d\n", ret2,(int)p2); 

    printf("At last, count equals to %d\n", count);             
}  
  
void* task1(void *arg1){  
    printf("task1 begin.\n");   
    count++;
    printf("task1 thread add count.\n");
    pthread_exit( (void *)1);   
}  
  
void* task2(void *arg2){  
    printf("thread2 begin.\n");  
    count ++;
    printf("task2 thread add count.\n");
    pthread_exit((void *)2);  
} 
上述程式碼的中主執行緒在usr()函式中建立了兩個執行緒,執行緒的屬性都是JOINABLE,即可以被其他執行緒收集返回資訊。然後等待兩個執行緒的返回,輸出返回資訊。


Terminal的輸出顯示thread2先於thread1執行,表明了這不是一個同步的程式,執行緒的執行是單獨進行的,由核心執行緒排程來進行的。為了區別程序,在程式碼中也加入了count++操作。最後在主執行緒中輸出count=2,即count被計數了2次,子執行緒被允許使用同一個程序內的共享變數,區別了程序的概念。

由於執行緒順序、時間的不確定性,往往需要對程序內的一個共享變數進行讀寫限制,比如加鎖等。

總結


        多程序和多執行緒的概念可以概括為一條河流。

1.縱向的是多執行緒,程序就像河流,執行緒就是分流,執行緒從一個程序中衍生出來。河流可以把水引向分流分擔自身的流水量,且各個分流是同時流水的(併發),它們共享了一個源頭(共享變數);

2.橫向的是多程序,一條河流從源頭上幾乎完全與另一條河流一樣,但由於一些環境的不同,造成了兩者最後的流向、各個分流的流量可能會不一樣,但是兩條河流同時在流水(併發)。