文章目錄
- 什么是多線程
- 為什么稱linux下的線程是輕量級進程呢?
- 線程的優點
- 線程的缺點
- 線程異常
- 線程和進程
- 創建線程
- 1.pthread_create
- 2.pthread_self
什么是多線程
進程是正在運行的程序的實例,而線程(thread)是進程中的一個執行路線。一個進程可以擁有多個線程。從程序的角度上來說,線程是一個獨立運行程序的片段。當程序運行時,進程把大部分資源合理分配給每個執行流(線程),且所有線程共享進程的地址空間,所以線程實際上就是一個輕量級的進程。下面給出進程中的線程示意圖:
值得注意的是,windos下的線程是有線程控制塊(TCB)的。而linux下的進程控制塊和線程控制塊都是task_struct
。
為什么稱linux下的線程是輕量級進程呢?
這是因為linux內核并沒有單獨為線程設計一套管理方案,而是通過相同的機制來管理進程和線程,只不過線程擁有的資源是進程的一部分,所以稱linux下的線程是輕量級進程。并且,Linux內核中的調度器并不區分線程和進程,可以是單線程的進程,也可以只是一個線程。為了讓用戶使用起來區分線程和進程,linux向上(用戶態)提供了POSI標準的線程接口(如pthread庫),在內核用
clone
系統調用創建和管理線程。盡管有線程這個模型,但底層還是輕量級的進程。
總結:==線程是共享同一進程的地址空間和資源的執行單元=。
線程的優點
- 共享資源:同一進程內的線程共享地址空間,能訪問相同的全局變量,堆和文件描述符等。這種共享使得線程間通信變得更加高效。
- 獨立的執行流:每個線程都有自己的程序計數器、寄存器和棧,這使得線程可以獨立執行。線程的獨立性使得多個線程并行執行多個任務,提高了此程序的響應性和吞吐量。
- 輕量級:相比于進程,線程的創建開銷會小很多,且不需要分配獨立的地址空間。上下文切換也比進程快,因為不涉及地址空間的切換。
4.并發執行:在多核處理器上,不同的線程可以做到真正的并行執行
線程的缺點
- 同步復雜性:由于共享進程空間,多個線程同時訪問和修改共享數據時可能會導致數據不一致等問題
- 性能損失:使用鎖和其它同步機制會導致性能下降
- 調試難度提高:編寫和調試一個多線程程序要比單線程程序困難得多
線程異常
- 單個線程如果出現除0或者訪問野指針等問題導致線程崩潰,進程也會隨著崩潰
- 進程終止,該進程的所有線程都會終止。這也就意味著,如果某一個線程出了異常進而終止進程的話,其它的線程也都會被終止。這也是線程不安全的原因之一。
線程和進程
- 進程是資源分配的基本單位,而線程是調度的基本單位。
- 具體來說,線程共享以下進程資源:
- 代碼段和數據段
- 文件描述符表
- 每種信號的處理方式即handler表
- 環境變量包括當前工作目錄
- 用戶id和組id
- 雖然線程共享進程的數據,但有屬于自己的一些數據:
- 線程ID
- 一組寄存器
- 棧
- errno錯誤流
- 信號屏蔽字
- 調度優先級
進程和線程的關系如下圖:
創建線程
在linux中,通常使用POSIX線程庫,即pthread庫。pthread庫提供了一組用于線程創建、管理和同步的函數,這些函數被包含在pthread.h
頭文件中。pthread庫的主要包含了線程管理、線程同步、線程屬性相關的函數,下面介紹線程管理中的一些常用函數。
1.pthread_create
功能:創建一個新的線程
原型:
#include<pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *
(*start_routine)(void*), void *arg);
pthread_t
是一個無符號整數thread
是指向pthread_t
變量的一個指針,用于存儲創建線程的標識符ID(輸出型參數)pthread_attr_t
類型是一個線程屬性的類,該類定義了線程的所有屬性,包括分離狀態、棧的大小等attr
是一個指向const pthread_attr_t
對象的指針,用于初始化被創建線程的屬性。如果設置為NULL
,則使用默認屬性start_rountine
是一個函數指針,該函數的參數和返回值類型都是void*
。表示線程線程執行的函數。arg
是傳遞給線程函數的參數。可以是NUL
L,如果需要傳遞多個參數,可以將其打包成結構體類型對象傳進去。同樣如果想返回多個值,可以將值打包成一個結構體再返回。- 創建成功返回0。失敗則返回一個非0值,表示錯誤代碼。常見得到錯誤碼有:
EAGAIN:
系統資源不足,無法創建更多線程。EINVAL
:無效的線程屬性EPERM
:沒有足夠的權限設置線程屬性
給出代碼樣例,演示使用pthread_create創建線程:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>using namespace std;void *rout(void *arg)//線程執行函數
{while (true){cout << "i am thread num: " << *(int *)arg << endl;sleep(1);}return NULL;
}int main()
{pthread_t tid;int num = 10;int res = pthread_create(&tid, NULL, rout, (void *)(&num));if (res != 0){//錯誤碼檢查fprintf(stderr, "pthread_create: %s\n", strerror(res));exit(1);}while (true){cout << "I am main thread" << endl;sleep(1);}return 0;
}
這樣我們就成功的使用pthread_create函數創建了一個線程。值得注意的是,執行main函數的線程我們稱為主線程。此外,一個線程可以使用pthread_self函數來獲取自己的線程ID.
2.pthread_self
功能:獲得當前線程的ID
函數原型:
pthread_t pthread_self(void);
于是我們可以將前面的代碼樣例改一下,觀察結果線程ID:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>using namespace std;void *rout(void *arg)
{while (true){cout << "thread id : " << pthread_self() << " i am thread num: " << *(int *)arg << endl;sleep(1);}return NULL;
}int main()
{pthread_t tid;int num = 10;int res = pthread_create(&tid, NULL, rout, (void *)(&num));if (res != 0){fprintf(stderr, "pthread_create: %s\n", strerror(res));exit(1);}while (true){cout << "tid: " << tid << " I am main thread" << endl;sleep(1);}return 0;
}
我i們可以觀察到,線程ID是一個非常復雜的數字,這個具體數值通常是由線程庫內部實現的,可能會使用內存地址等機制來生成唯一的線程ID。