文章目錄
- Linux線程
- 1. 線程的概念
- 1.1 什么是線程
- 2. 線程的特點
- 2.1 線程的優點
- 2.2 線程的缺點
- 2.4 線程和進程
- 3. 線程函數的使用
- pthread_create() 創建線程
- pthread_self() 獲取線程ID
- pthread_exit() 線程終止
- pthread_cancel() 線程取消
- pthread_join() 線程等待
- pthread_detach()線程分離
Linux線程
1. 線程的概念
??我們之前認識到,進程是程序的一個執行實例,是正在執行的程序等,這是從進程本身來看的,從內核上看,進程擔當分配系統資源(CPU時間,內存)的實體,是聯系硬件和軟件的橋梁。
??通常進程包含:進程獨立的程序地址空間和頁表,通常是我們說的虛擬內存;
進程擁有系統分配的各種資源;標識進程唯一性和保存執行信息的進程控制塊(PCB);執行的程序代碼和相關的數據;打開的文件和設備;進程上下文。
??當一個進程要執行大量任務的時候,一個單進程通常只有一個執行流,只能執行一個時間片,這樣無法提高計算機的運行效率,所以我們要在進程中使用線程。
??通過線程,我們可以提高計算機的運行效率。線程是進程中的執行單元,多個線程可以共享進程的資源,如內存空間、文件描述符等。這樣使得多個線程可以在同一個進程中并發執行,將復雜的任務分解為多個子任務,每一個子任務有線程執行,充分利用CPU的時間片,減少等待時間,提高系統運行效率。
??簡單總結:
??進程因為包含很多資源,開銷較大,線程在進程內部開銷較小。
??多線程有多個時間片且并發執行,資源共享,運行效率較高。
??
1.1 什么是線程
??(1)在一個程序里的一個執行路線就叫做線程(thread)。更準確的定義是:線程是“一個進程內部的控制序列”。
??(2)一切進程至少都有一個執行線程。
??(3)線程在進程內部運行,本質是在進程地址空間內運行。
??(4)在Linux系統中,在CPU眼中,看到的PCB都要比傳統的進程更加輕量化,所以Linux 下的線程看作輕量級進程。
??(5)透過進程虛擬地址空間,可以看到進程的大部分資源,將進程資源合理分配給每個執行流,就形成了線程執行流。
??總結:線程是進程中的一個實體,作為系統調度和分派的基本單位。
??
2. 線程的特點
2.1 線程的優點
??(1)創建一個新線程的代價要比創建一個新進程小得多
??(2)與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多
??(3)線程占用的資源要比進程少很多
??(4)能充分利用多處理器的可并行數量
??(5)在等待慢速I/O操作結束的同時,程序可執行其他的計算任務
??(6)計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現
??(7)I/O密集型應用,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
??
2.2 線程的缺點
??(1)性能損失:一個很少被外部事件阻塞的計算密集型線程往往無法與共它線程共享同一個處理器。如果計算密集型線程的數量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的同步和調度開銷,而可用的資源不變。
??(2)健壯性降低:編寫多線程需要更全面更深入的考慮,在一個多線程程序里,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。
??(3)缺乏訪問控制:進程是訪問控制的基本粒度,在一個線程中調用某些OS函數會對整個進程造成影響。
??(4)編程難度提高:編寫與調試一個多線程程序比單線程程序困難得多
??
2.4 線程和進程
進程和線程的區別:
??(1)調度單位:進程是資源分配的基本單位,線程是調度的基本單位。
??(2)資源擁有:進程擁有獨立的地址空間和系統資源,線程共享進程資源,但也擁有自己的一部分資源:線程ID、一組寄存器、棧、errno、信號屏蔽字、調度優先級。
??(3)系統開銷:創建或撤銷進程時,系統需要分配或回收資源,開銷較大;線程的創建和撤銷開銷相對較小。
??(4)通信方式:進程間通信較為復雜,需要使用特定的進程間通信機制,如管道、消息隊列等;線程間通信相對簡單,可以通過共享內存等方式直接進行。
??(5)健壯性:進程間相互獨立,一個進程的崩潰一般不會影響其他進程;線程一個線程的崩潰可能影響整個進程。
??此外因為進程的多個線程共享同一地址空間,因此Text Segment、Data Segment都是共享的,如果定義一個函數,在各線程中都可以調用,如果定義一個全局變量,在各線程中都可以訪問到,除此之外,各線程還共享以下進程資源和環境:文件描述符表,每種信號的處理方式(SIG_ IGN、SIG_ DFL或者自定義的信號處理函數)當前工作目錄,用戶id和組id。
??
3. 線程函數的使用
??POSIX線程庫:與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的,要使用這些函數庫,要通過引入頭文<pthread.h>,鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項。
pthread_create() 創建線程
功能:創建一個新的線程
原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void*), void *arg);
參數:
??thread:返回線程ID
??attr:設置線程的屬性,attr為NULL表示使用默認屬性
??start_routine:是個函數地址,線程啟動后要執行的函數
??arg:傳給線程啟動函數的參數
返回值:
??成功返回0;失敗返回錯誤碼
錯誤檢查:
??傳統的一些函數是,成功返回0,失敗返回-1,并且對全局變量errno賦值以指示錯誤。
??pthreads函數出錯時不會設置全局變量errno(而大部分其他POSIX函數會這樣做)。而是將錯誤代碼通過返回值返回pthreads同樣也提供了線程內的errno變量,以支持其它使用errno的代碼。
??對于pthreads函數的錯誤,建議通過返回值判定,因為讀取返回值要比讀取線程內的errno變量的開銷更小。
??
進程地址空間布局
??pthread_ create函數會產生一個線程ID,存放在第一個參數指向的地址中。該線程ID和前面說的線程ID不是一回事。
??前面講的線程ID屬于進程調度的范疇。因為線程是輕量級進程,是操作系統調度器的最小單位,所以需要一個數值來唯一表示該線程。
??pthread_ create函數第一個參數指向一個虛擬內存單元,該內存單元的地址即為新創建線程的線程ID,屬于NPTL線程庫的范疇。線程庫的后續操作,就是根據該線程ID來操作線程的。
??線程庫NPTL提供了pthread_ self函數,可以獲得線程自身的ID。
??pthread_t 到底是什么類型呢?取決于實現。對于Linux目前實現的NPTL實現而言,pthread_t類型的線程ID,本質就是一個進程地址空間上的一個地址。
??
pthread_self() 獲取線程ID
pthread_t pthread_self(void);
功能:通過 pthread_self() 函數可以獲取到當前線程的 ID。
??
pthread_exit() 線程終止
功能:線程終止
原型:
void pthread_exit(void *value_ptr);
參數:
??value_ptr:value_ptr不要指向一個局部變量。
返回值:
??無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)。
??如果需要只終止某個線程而不終止整個進程,可以有三種方法:
??(1)從線程函數return。這種方法對主線程不適用,從main函數return相當于調用exit。
??(2)線程可以調用pthread_ exit終止自己。
??(3)一個線程可以調用pthread_ cancel終止同一進程中的另一個線程。
??需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。
??
pthread_cancel() 線程取消
功能:取消一個執行中的線程。
原型:
int pthread_cancel(pthread_t thread);
參數:
??thread:線程ID
返回值:
??成功返回0;失敗返回錯誤碼。
??
pthread_join() 線程等待
功能:等待線程結束
原型:
int pthread_join(pthread_t thread, void **value_ptr);
參數:
??thread:線程ID
??value_ptr:它指向一個指針,后者指向線程的返回值
返回值:
??成功返回0;失敗返回錯誤碼
??
為什么需要線程等待?
??1. 已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
??2. 創建新的線程不會復用剛才退出線程的地址空間。
??
??調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:
??(1)如果thread線程通過return返回,value_ ptr所指向的單元里存放的是thread線程函數的返回值。
??(2)如果thread線程被別的線程調用pthread_ cancel異常終掉,value_ ptr所指向的單元里存放的是常數PTHREAD_ CANCELED。
??(3)如果thread線程是自己調用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數。
??(4)如果對thread線程的終止狀態不感興趣,可以傳NULL給value_ ptr參數。
??
pthread_detach()線程分離
??功能:將一個線程設置為分離狀態。當一個線程被分離后,它所占用的資源在其終止時會由系統自動回收,而無需其他線程通過 pthread_join 來等待并回收資源
int pthread_detach(pthread_t thread);
??默認情況下,新創建的線程是joinable的,線程退出后,需要對其進行pthread_join操作,否則無法釋放資源,從而造成系統泄漏。
??如果不關心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統,當線程退出時,自動釋放線程資源。
??可以是線程組內其他線程對目標線程進行分離,也可以是線程自己分離:
pthread_detach(pthread_self());
??
使用多線程模擬一個簡單的多線程加減乘除計算過程
#include <iostream>
#include <pthread.h>
#include <vector>
#include <cstring>
#include <string>
#include <cstdlib>
#include <unistd.h>
#include <ctime>char op[4]={'+','-','*','/'};//計算請求
class Request
{
public:Request(int id,int n1,int n2,int op):_id(id),_n1(n1),_n2(n2),_op(op){}
public:int _id;int _n1;int _n2;char _op;
};//計算結果
class Response
{
public:Response(std::string request, int result):_request(request),_result(result),_errno(0){}
public:std::string _request;int _result;int _errno;
};//執行計算的函數
void* handler(void *args)
{Request *rq=static_cast<Request*>(args);std::string req="thread "+std::to_string(rq->_id)+" : "+std::to_string(rq->_n1)\+rq->_op+std::to_string(rq->_n2);int res,flag=0;switch (rq->_op) {case '+':res = rq->_n1 + rq->_n2;break;case '-':res = rq->_n1 - rq->_n2;break;case '*':res = rq->_n1 * rq->_n2;break;case '/':if (rq->_n2 != 0) {res = rq->_n1 / rq->_n2;} else {flag=1;std::cerr << "Error: Division by zero!" << std::endl; }break;}Response *rsp=new Response(req,res);if(flag==1) rsp->_errno=1;std::cout<<"thread is working"<<std::endl;//std::cout<<rq->_id<<" thread result : "<<rq->_n1<<"+"<<rq->_n2<<"="<<rsp->_result<<std::endl;//usleep(1000);return rsp;
}//我們使用多線程模擬一個簡單的多線程加減乘除計算過程
void test2()
{//設置隨機數種子std::srand(static_cast<unsigned int>(std::time(nullptr)));//模擬10次加減乘除計算過程std::vector<pthread_t> threads;std::vector<Response*> responses; // 用于存儲每個線程的返回值for(int i=1;i<=10;i++){pthread_t tid;int n1=rand()%10;int n2=rand()%10;Request *rq=new Request(i,n1,n2,op[n1%4]);pthread_create(&tid,nullptr,handler,rq);threads.push_back(tid); // 將線程句柄存儲起來}for(auto& thread:threads){void *ret;pthread_join(thread,&ret);Response *rsp = static_cast<Response *>(ret);std::cout<<rsp->_result<<std::endl;std::cout << "rsp->result: " << rsp->_result <<std::endl; responses.push_back(rsp); // 將 Response 對象存儲起來}// 輸出每個線程的計算結果for (auto rsp : responses){if(rsp->_errno==1) {std::cout <<rsp->_request << " result: " << "division by zero error" << std::endl;}else {std::cout <<rsp->_request << " result: " << rsp->_result << std::endl;}delete rsp; // 釋放每個 Response 對象的內存}
}
??