學習整理——多程序和多執行緒概念理解
程序
一個程序,包括了程式碼、資料和分配給程序的資源(記憶體),在計算機系統裡直觀地說一個程序就是一個PID。作業系統保護程序空間不受外部程序干擾,即一個程序不能訪問到另一個程序的記憶體。有時候程序間需要進行通訊,這時可以使用作業系統提供程序間通訊機制。通常情況下,執行一個可執行檔案作業系統會為其建立一個程序以供它執行。但如果該執行檔案是基於多程序設計的話,作業系統會在最初的程序上創建出多個程序出來,這些程序間執行的程式碼是一樣,但執行結果可能是一樣的,也可能是不一樣的。
為什麼需要多程序?最直觀的想法是,如果作業系統支援多核的話,那麼一個執行檔案可以在不同的核心上跑;即使是非多核的,在一個程序在等待I/O操作時另一個程序也可以在CPU上跑,提高CPU利用率、程式的效率。
在Linux系統上可以通過fork()來在父程序中創建出子程序。一個程序呼叫fork()後,系統會先給新程序分配資源,例如儲存資料和程式碼空間。然後把原來程序的所有值、狀態都複製到新的程序裡,只有少數的值與原來的程序不同,以區分不同的程序。fork()函式會返回兩次,一次給父程序(返回子程序的pid或者fork失敗資訊),一次給子程序(返回0)。至此,兩個程序分道揚鑣,各自執行在系統裡。
上述程式碼在fork()之後進行了一次判斷,以辨別當前程序是父程序還是子程序。#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; }
為了說明父程序與子程序有各自的資源空間,設定了對count的計數。Terminal輸出如下:、
明顯兩個程序都執行了count++操作,但由於count是分別處在不同的程序裡,所以實質上count在各自程序上只執行了一次。
執行緒
執行緒是可執行程式碼的可分派單元,CPU可單獨執行單元。在基於執行緒的多工的環境中,所有程序至少有一個執行緒(主執行緒),但是它們可以具有多個任務。這意味著單個程式可以併發執行兩個或者多個任務。
也就是說,執行緒可以把一個程序分為很多片,每一片都可以是一個獨立的流程
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.橫向的是多程序,一條河流從源頭上幾乎完全與另一條河流一樣,但由於一些環境的不同,造成了兩者最後的流向、各個分流的流量可能會不一樣,但是兩條河流同時在流水(併發)。