個人主頁 : 個人主頁
個人專欄 : 《數據結構》 《C語言》《C++》《Linux》
文章目錄
- 前言
- 一、線程的概念
- 線程代碼的簡單示例
- 總結
前言
本文是對于線程概念的知識總結
一、線程的概念
在課本上,線程是比進程更輕量級的一種指向流 或 線程是在進程內部執行的一種執行流。
我們再提出兩個理解,線程是CPU調度的基本單位 / 進程是承擔系統資源的基本實體。
先記住上面的結論
我們知道,進程 = 內核數據結構 + 代碼和數據構成的。
CPU要調度進程,就要有運行隊列,而運行隊列中排隊的就是pcb。CPU通過這些pcb,找到對應的地址空間,進而通過地址空間中的虛擬地址,在頁表中映射物理地址,從而找到對應的代碼和數據。那么,我們是不是可以將地址空間理解為進程的資源窗口,畢竟進程想要訪問正文代碼,數據,new和malloc的空間,共享庫,棧上的臨時數據,命令行參數和環境變量等都是通過地址空間來進行的。
那么,我們如果要創建進程,就要創建對應的pcb,地址空間,將磁盤中的代碼和數據加載進內存,再將地址空間中的虛擬地址與物理地址映射構成頁表,打開stdin,stdout,stderr構建文件資源描述表,初始化信號處理過程等,這樣看來進程創建的成本還挺高的。那為了減少成本,我們能不能在進程內部,再創建多個pcb指向該進程的地址空間,將代碼分成多個,并將私有的數據,使每個pcb各自私有一份,可以共享的數據就共享。當CPU來調度其中一個pcb時,其只會運行該進程的一部分代碼和一部分數據。我們就可以將這種比以往進程更輕(創建成本)的東西,稱為線程。
在linux程序員看來,描述線程的結構體(TCB Thread control block ) 中屬性在pcb中都有。那如果我們把pcb來充當tcb,我們就可以把進程調度,切換的代碼在線程級別復用起來,而不用再單獨設計線程。也就說,以后再創建線程,只需要創建pcb,然后指向同一個進程地址空間,線程的管理就可以復用進程的管理代碼。這就是linux中線程的實現方案。
那就有一個問題,在CPU看來,一個pcb到底是進程還是線程,或者說CPU要不要區分一個pcb是進程還是線程。答案很明顯,CPU不需要區分進程和線程,CPU只需要根據pcb的地址空間來執行代碼即可。也就是現在CPU拿到一個pcb,其執行流是小于等于進程的(當該進程內有多個pcb,其執行流小于進程;當該進程只有一個pcb,其執行流等于該進程)。那現在什么是進程?進程 = 該進程的所有pcb + 地址空間 + 頁表 + 代碼和數據。與以往進程的區別就是,現在進程內部有多個執行流,以前進程內部只有一個執行流。
紅色框內的所有東西之和就是進程。
現在我們就可以理解進程是承擔分配系統資源的基本實體,線程是參與資源分配。進程創建要申請系統資源,來創建一個pcb,地址空間,頁表,代碼和數據,線程創建就是創建一個pcb來分配該進程內部的資源(劃分地址空間)。實際上,在linux中并沒有真正意義的線程,只是用進程的數據結構來模擬的線程。這種描述執行流的pcb就是輕量級進程(LWP light wigth process 執行流小于等于進程)。那以后,CPU調度就不再是進程,而是一個一個的輕量級進程(pcb),也就是線程是CPU調度的基本單位。
線程比進程更輕量化的原因
- 線程創建銷毀更簡單,線程只需創建銷毀一個pcb來參與資源的分配,而進程創建銷毀不僅僅只需要一個pcb
- 線程在地址空間中運行
- 線程調度更簡單;在同一進程內,線程之間切換是不需要更改地址空間和頁表,只需要將運行中產生的臨時數據進行切換即可,也就是只需切換少量的上下文數據。但這不是主要原因,在cpu內有一個大的存儲空間cache用來進行數據的緩存(熱數據),cache在緩存中是以進程為單位的,那理論上,線程做切換,就不需要切換cache,著就是線程切換更簡單。因為有局部性原理(如當前訪問的代碼附近的代碼,有可能是下次要訪問的代碼)給預加載機制,提供理論基礎,
線程代碼的簡單示例
經過上面的描述,我們已經對線程有了一定的理解,下面就讓我們在代碼層面上來看看。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>// 新線程
void *ThreadRountine(void *arg)
{const char *threadname = (const char *)arg;while (true){std::cout << "I am a new thread: " << threadname << ", pid: " << getpid() << std::endl;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRountine, (void*)"thread 1");// 主線程while (true){std::cout << "I am main thread" << ", pid: " << getpid() <<std::endl;sleep(1);}return 0;
}
上面代碼,我們創建了一個新線程,并讓主線程和新線程都執行死循環。
不出所料,只有一個進程在執行,主線程和新線程都在執行,并且pid相同(在同一個進程內)。那如何查看線程呢? ps -aL查看。
果然有兩個線程,其中主線程的LWP 和 PID是相同的。在操作系統中,是通過LWP來識別不同的輕量級進程的。
#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>int gnt = 100;
// 新線程
void *ThreadRountine(void *arg)
{const char *threadname = (const char *)arg;while (true){std::cout << "I am a new thread: " << threadname << ", gnt = " << gnt << ", &gnt" << &gnt << std::endl;gnt--;sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, ThreadRountine, (void*)"thread 1");// 主線程while (true){std::cout << "I am main thread" << ", gnt = " << gnt << ", &gnt" << &gnt <<std::endl;sleep(1);}return 0;
}
上述代碼,我們創建了兩個線程,其中新線程式gnt–,兩個線程都打印gnt的值和地址。
可以發現兩個線程共享全局變量gnt。
總結
以上就是我對于線程概念的理解和知識總結。