前言:大家早上中午晚上好!!今天來學習一下linux系統下所謂的線程吧!!!
一、重新理解進程,什么是進程?
1.1 圖解
其中黑色虛線部分一整塊就是進程,注意:一整塊指的是:①:從一個程序運行起來后成為進程,OS堆這個進程進行描述PCB結構、②這個PCB中的進程地址空間所映射的物理內存資源、③頁表,等等。。。總而言之:所有保證這個進程正常運行的所有資源!!
也就是說:進程是系統資源分配的基本單位,每一個進程的資源由系統分配,分配出來后的整一塊資源都屬于進程,進程相當于大家庭;
1.2、Linux系統下的線程
圖解:
圖中通過第一個創建的PCB描述出來的PCB2、PCB3、PCB4、PCB5暫時可以把它理解為線程!!(注意:只是暫時把它認為是線程);
這些通過主線程描述出來的PCB2、3、4、5.....等他們共同瓜分進程地址空間,而這個地址空間就是資源的窗口(通過頁表跟物理內存建立一一映射關系),所以相當于同一進程下所有線程在同一塊資源下進行瓜分,然后這些線程就可以分別執行自己的任務!!!(所以線程相當于家庭中的每一個成員:爸爸、媽媽、爺爺、奶奶、哥哥、姐姐、等!!)
1.3線程的調度
創建并完成資源分配后,cpu就可以對一個個PCB進行調度了,讓他們分別執行代碼中的某一部分內容,各自完成自己的任務!!所以:線程是cpu調度的基本單位
二、pthread庫
2.1、pthread庫是什么先不管,先用,先寫一段代碼
寫一段代碼實現創建一個線程,并讓它執行自己的任務:
寫之前先介紹一個創建線程的接口:
首先第一個參數為:輸出型參數,需要自己定義pthread_t 類型變量,把這個變量地址傳入,創建完成后獲取到pthread_t;
第二個參數:線程屬性,不需要修改設置為nullptr即可;
第三個參數:線程的入口函數,這個線程一被調用就會從入口函數開始執行,這個函數自己定義,這個函數的返回值是void*類型,參數為void*類型;
第四個參數:給入口函數傳送的形參,如果不許要傳設置為nullptr;
創建完線程,線程跑完后要對線程進程回收;
介紹一個等待線程的接口:
第一個參數:為線程的tid,傳入哪個tid就等待哪個線程;
第二個參數為:獲取這個線程的退出信息,如果不需要設置為nullptr;
返回值int:如果等待成功返回0,如果失敗返回錯誤碼;
有了這兩個接口我們就來寫一段代碼吧:
#include <iostream>
using namespace std;
#include <pthread.h>
#include <unistd.h>
void* print(void* args)
{int cnt=10;while(cnt--){cout<<"i am a thread !!!!"<<endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,print,nullptr);//線程創建int cnt2=10;while(cnt2--){cout<<"i am a main thread!!!!"<<endl;sleep(1);}int n=pthread_join(tid,nullptr);//線程等待if(n==0)cout<<"wait success!!!"<<endl;return 0;
}
當我們編譯的時候發現報錯:
原因是因為pthread.h是動態庫,我們編譯的時候需要加上 -lpthread 才能鏈接成功:
makefile:
mythread:mythread.ccg++ -o $@ $^ -std=c++11 -lpthread //編譯時加上-lpthread
.PHONY:clean
clean:rm -f mythread
編譯成功并生成mythread可執行程序:
運行:
我們看到主線程和分線程分別打印了10次,并成功等待了線程;
2.2 批量創建線程
上面我們創建的是一個線程,如果我們想批量創建線程呢?
那很簡單,用一個for循環創建線程同時把tid保存起來,再用一個for循環等待tid:
同時我們每個線程獲取一下pid_t(進程pid),同時每個線程獲取一下pthread_t(tid),同時用輕量級命令 :ps- aL 查看所有輕量級進程:
#include <iostream>
using namespace std;
#include <pthread.h>
#include <unistd.h>
#include <vector>
#define THREAD_NUM 5
void* print(void* args)
{cout<<"i am a thread !!!! tid is :"<<pthread_self()<<" pid is:"<<getpid()<<endl;sleep(1);return nullptr;
}
int main()
{vector<pthread_t> tids;//批量創建線程并保存tidfor(int i=0;i<THREAD_NUM;i++){pthread_t tid;pthread_create(&tid,nullptr,print,nullptr);tids.push_back(tid);}for(int i=0;i<THREAD_NUM;i++){int n=pthread_join(tids[i],nullptr);//線程批量等待if(n==0)cout<<"wait success!!! tid is : "<<tids[i]<<endl;}return 0;
}
編譯后運行:
同時命令行輸入ps - aL:
我們發現所有線程它的pid 都是相同的,tid是一串很長的數字且都是不相同的,用ps -aL命令查看到LWP中只一個數字15304是跟pid一樣其他都不相同!!
pthread_t tid 究竟是什么?LWP是什么?他們跟PID之間的關系是什么?還有pthread庫又是什么?
2.3重新理解線程
①:在linux系統下一般把線程稱為輕量級進程,因為在Linux內核中沒有用新的數據結構來描述線程,而是通過原有的進程PCB來描述線程的!!
②: 因此linux系統是通過LWP來對這些所謂的 “線程”?進行調度的,LWP就是:light weight process (輕量級進程的意思),所以在linux下所謂的線程:就是一個個通過(第一個創建出來的進程的PCB)描述出來的輕量級進程!
③: 那么linux系統不創建新的數據結構來描述線程,等于linux不想管理這些線程,那么誰來管理?pthread庫!!來了!pthread庫是第三方庫,當我們安裝linux操作系統的時候必須把pthread庫安裝好,這個pthread庫里有別人編寫好的一系列對線程操作的接口,且這個庫需要管理每一個用戶創建出來的線程!!!怎么管理?->先描述再組織!->pthread庫必須有一個描述線程的數據結構(TCB)->pthread庫通過對一個個TCB的管理從而管理一個個線程!!
④線程由pthread庫管理,pthread庫不屬于OS內核,所以線程不屬于OS內核!!在linux下所謂的線程是用戶級線程!!
當pthread庫調用一系列的pthread_create、pthread_join、等接口時其底層調用的其實是clone這個系統調用接口:
圖解:
所以我們可以回答上面的第一個問題了:pthread_t tid 究竟是什么:
pthread_t tid 是用戶級線程的tid!!所謂用戶級就是管理線程的pthread庫屬于用戶級!!每一個TCB有其對應的tid,用戶通過tid便可以指定對某個線程進行操作!!
然后我們還能回答第二個問題了:LWP是什么:
圖解:
我們知道每個PCB都有它的PID,linux下的“線程”是通過進程的PCB描述出來的;
當它被描述出來之后就是一個所謂的“線程”了,也就是稱為了cup調度的基本單位!
而PID代表的是進程,所謂進程代表的是被分配到的整塊資源,所有“線程”共享的這一個塊資源;
因此所有的“線程”的PID只有一個!!這些新創建出來的“線程”所以用LWP來標志!!
如果LWP跟PID一樣說明這是第一個被創建出來的“線程”,即主線程!!!
最后cup就能通過LWP調度到每個它想調度的“線程”!!
同時我們可以回答第三個問題:pthread庫是什么:
因為linux內核不管理線程,所以linux中把線程稱之為:輕量級進程(它管理的是進程);
因為線程需要被管理,所以linux必須安裝有pthread庫;
pthread庫就是用來管理用戶創建除了的一切線程;
既然要管理所以pthread庫必須對線程進行描述,必須要有TCB結構,通過一個個TCB結構的管理實現對所有線程的管理;
因此Linux下的所謂線程其實是用戶級線程,那么我們可以這么認為:Linux下的線程=LWP(linux內核中的PCB輕量級進程標志)+pthread_t TID(用戶中的TCB線程的標志);
三、線程的控制
3.1 線程的分離
如果主線程不想等待分支線程,我們可以讓線程自己分離,分離后由OS進行;
線程分離接口:
#include<pthread.h>
int pthread_detach(pthread_t thread);
//1. 這是用來分離線程的pthread庫接口
//2. thread:你想分離的線程的tid
//3. 返回值int,如果分離成功返回0,如果分離錯誤返回錯誤碼
獲取當前線程tid接口:
#include <pthread.h>
pthread_t pthread_self(void);
//1. pthread_t 返回值如果獲取成功返回當前線程的tid
代碼:
我們分離的同時等待一下線程看看能否等待成功:
線程分離的兩種方法:線程自己分離:
#include <iostream>
using namespace std;
#include <pthread.h>
#include <unistd.h>
#include <vector>
#include <cstring>
#define THREAD_NUM 5
void* print(void*args)
{pthread_detach(pthread_self());cout<<"i am a thread!!"<<endl;return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,print,nullptr);sleep(1);//等待1s讓線程分離完成int code=pthread_join(tid,nullptr);if(code==0){cout<<"wait success !!"<<endl;}elsecout<<"wait false!! error num is:" <<code<<"error message :"<<strerror(code)<<endl;return 0;
}
編譯運行:
線程分離成功,主線程等待失敗,返回22錯誤碼,意思是無效的參數,也就是沒有等待到對應的tid;
線程分離方法二:主線程讓他分離:
編譯運行:
結果一樣,說明成功分離分支線程;
3.2 線程的退出
線程退出的幾種方式:
①線程運行完走到return 正常退出;
②終止線程的接口:
#include<pthread.h>
void pthread_exit(void* retval);
//1. retval 可以傳入退出信息,由外部線程等待函數獲取
我們退出的同時可以傳入參數retval讓主線程獲取退出信息:
主線程等待獲取線程退出信息需要傳入一一個二級參數,因為join的第二個參數是一個輸出型參數:
void* print(void*args)
{cout<<"i am a thread!!"<<endl;pthread_exit((void*)100);return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,print,nullptr);sleep(1);void*retval;pthread_join(tid,&retval);(long long int)retval;printf("%d\n",retval);return 0;
}
編譯運行:
100退出碼被帶了出來;
③還有一個線程會終止的場景:當同一線程組內的任一線程出現異常,接收到終止信號,那么整個進程會崩潰,所有線程都會退出;
3.3 線程的取消
主線程可以取消一個線程,前提是這個線程已經被啟動:
取消線程的接口:
#include<pthread.h>
int pthread_cancel( pthread_t thread);
//1. 取消一個線程,前提這個線程被啟動
//2. thread : 需要取消的線程的tid
//3. 如果成功返回0,失敗返回一個錯誤碼
void* print(void*args)
{while(true){cout<<"i am a thread!!"<<endl;sleep(1);}return nullptr;
}
int main()
{pthread_t tid;pthread_create(&tid,nullptr,print,nullptr);int code= pthread_cancel(tid);if(code==0){cout<<"取消成功"<<endl;}elsecout<<"取消失敗"<<endl;//sleep(1);//線程已被啟動pthread_join(tid,nullptr);return 0;
}
運行:
四、總結線程的特點
4.1 、線程是系統調度的基本單位,進程是資源分配的基本單位;
4.2、線程的粒度小于進程,占用資源更少,因此通常多線程比多進程更高效;
4.3、線程沒有獨立的地址空間,線程是劃分進程的地址空間,本質上使用的是同一地址空間!線程不擁有系統資源,線程共享進程的資源!
4.4、大量的計算使用多線程和多進程都可以實現并行/并發處理,區別在于線程的資源消耗小于進程,線程的穩定性不如進程,因此需要具體更細致的需求場景;
4.5、線程除了獨立的棧空間和一組寄存器(保存線程的上下文)其他線程看不到之外其他所有資源都是所有線程共享的;
4.6、線程的通信速度更快,切換更快,因為他們在同地址空間內,且還共享了很多其他進程資源,比如頁表指針,這些是不需要切換的!
今天的分享就到這里!如果對你有所幫助記得點贊收藏+關注哦!!謝謝!!!
咱下期見!!!