主要介紹了為什么要有線程 和線程的調用 和簡單的對線程進行封裝。
背景知識
a.重談地址空間
我們知道物理內存的最小單元大小是4kB
物理內存是4G那么這樣的單元友1M個
操作系統先描述再組織struct page[1M]
對于32位數據字長的機器,頁表有2^32條也就是4G條,每一條光保存兩個地址加一個標記位 這個頁表就得有36G這樣大
實際上比如 0010 0010 0010 0010 0010 0010 0010 0010
虛擬地址>> 12 得到 頁框號
虛擬地址&&oxfff 得到 頁內偏移量
線程的概念
線程:在進程內部運行,是cpu調度的基本單位
進程:承擔系統資源的基本實體
進程:承擔系統資源的基本實體 怎么理解這句話呢?可以把進程看作家庭,國家的最小單位國是千萬家~~家是社會資源分配的最小單位,把線程看作個人,線程是進程的一部分
os要單獨設計線程 --新建?暫停?銷毀?調度?線程要不要和進程產生關聯
在windows 有 專門管理線程的數據結構
struct tcb
{
// 線程id
// 線程 優先級
// 狀態上下文
// 鏈接屬性
};
但是linux 中沒有專門的線程,Linux 是用進程模擬的線程
linux中的執行流,我們稱為輕量級進程
線程:在進程內部運行,是cpu調度的基本單位
在cpu看來被調用的都是輕量級進程
澄清進程和線程
進程 是 只有一個執行流的線程~
見一見線程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
pthread_t *thread: 一個指向pthread類型的指針 , pthread 這個類型用于保存的線程的標識符
const pthread_attr_t *attr: 線程屬性對象的引用,可以用來設置線程的優先級、調度策略等。如果不需要設置特殊屬性,可以傳入 NULL 使用默認屬性。
start_routine: 指向線程開始執行的函數的指針這個函數的類型應為 void ()(void*),意味著它接受一個 void * 類型的參數,并返回 void * 類型的結果。
arg: 傳遞給 start_routine 函數的參數,類型為 void *。這使得你可以在創建線程時向線程函數傳遞數據。
#include <iostream>
#include <unistd.h>void *threadStart(void * args)
{while(true){std::cout<<"new thraed running..."<<",pid:"<<getpid()<<std::endl;sleep(1);}
}int main()
{pthread_t tid;// 新線程pthread_create(&tid, nullptr, threadStart,(void *) "thread-new"); // 相當于把頁表分成了兩部分// 主線程while(true){sleep(1);std::cout<<"main thraed running..."<<",pid:"<<getpid()<<std::endl;}return 0;
}
有了多進程為什么還要有多線程
線程不用再創建頁表等,只需要一個pcb 創建的成本低
運行期間線程,的調度成本低,因為不需要切換頁表
刪除線程,比刪除一個進程的成本低,只需要刪除一個pcb就行了
線程的調度成本為什么更低呢?
我們切換頁表只需要修改對應寄存器中的值就可以了,這個不是主要原因
最主要的原因是
數據會被緩存在cache中,一切換進程,cache中的數據就作廢了,就需要重新cache了
為什么還要有進程呢?
線程的健壯性比較低,一個線程出錯整個程序都退出了
哪些東西在多線程中是共享的
因為同一地址空間,所以代碼段,數據段都是共享的,文件描述符表,每種信號的處理方式,工作目錄。
哪些東西是各自都有一份的呢?
線程id,錯誤碼,調度優先級,調度的上下文,信號屏蔽字
**最重要的:**一組寄存器,保存硬件上下文數據 線程是在調度運行的
棧:線程的棧主要用于存儲函數調用的局部變量、函數參數以及返回地址。每當線程調用一個新的函數時,相關的局部變量和函數參數就會被壓入該線程的棧中,形成一個新的棧幀。當函數返回時,相應的棧幀就會被彈出,恢復之前的調用環境。由于每個線程有獨立的棧,它們可以并發地進行函數調用,而不會相互干擾。
幾個基本的問題
問題1:主線程和新線程誰先運行?
系統會將這個新線程加入到就緒隊列中等待調度。調度器會根據其自身的算法(比如時間片輪轉、優先級等)來決定哪個線程獲得 CPU 時間片并開始執行。
問題2:我們期望誰最后退出? main thread,你如何保證呢?
我們希望主線程后退出,如果主線程先退出,主線程退出了整個進程就結束了,可能此時新線程還沒有完成任務呢。
我們用pthread_join 來保證 主線程后退出
void * RunThread(void * args)
{std:: string name;name = (const char *)args;std::cout <<name << std::endl;return args;
}
int main()
{pthread_t tid;pthread_create(&tid, nullptr, RunThread,(void *)"thread - 1");int n = pthread_join(tid,nullptr);if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}
問題3: tid 是什么樣子的?是什么呢?虛擬地址! 為什么?
void PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
我們打出來發現是一個地址
為什么是一個地址呢?我們等下再說
問題4:全面看待線程函數傳參: 我們可以傳遞任意類型,但你一定要能想得起來,也可以傳遞類對象的地址!!!
class ThreadData
{
public:int x;int y;
};
void * RunThread(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;return args;
}
void PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
int main()
{pthread_t tid1;ThreadData x1;x1.x = 10;x1.y =20;pthread_create(&tid1, nullptr, RunThread,(void *)&x1);// PrintToHEX(tid);int n = pthread_join(tid1,nullptr);if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}
問題6:注意剛剛我們x1的寫法其實是不標準的 為什么?
#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:int x;int y;
};
void * RunThread1(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;data->x = 5;data -> y = 10;std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;return args;
}
void * RunThread(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;return args;
}
void PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
int main()
{pthread_t tid1;ThreadData x1;x1.x = 10;x1.y =20;pthread_create(&tid1, nullptr, RunThread,(void *)&x1);pthread_t tid2;pthread_create(&tid2, nullptr, RunThread1,(void *)&x1);int n = pthread_join(tid1,nullptr);if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}
比如說這樣,我們原本想要打印 10 ,20 5,10 的 結果卻這樣。
因為main 棧上的變量 這兩個線程都可以看到并修改,且這個變量只有一份,當第一個線程正準備打印的時候,第二個線程修改了這兩個變量的值。導致第一個線程打印出來就和第二個線程一樣了。 根本原因就是 變量只有一份,我們多創建一份就好了。
而且我們不推薦直接 將main棧上的變量給新線程因為 main函數棧的變量生命周期是隨main函數的,可能新線程早都結束了,但是main棧上的變量就是不結束
我們用new 可以在新線程中方便管理 傳過來的變量的生命周期。
問題5: 全面看待線程函數返回
它也可以傳遞自定義類型哦
#include <iostream>
#include <pthread.h>
#include <string>
class ThreadData
{
public:int x;int y;int Excute(){return x + y; }
};
class ThreadResult
{
public:int x;int y;int ans;void print(){std::cout<<std::to_string(x)+"+"+std::to_string(y)+"="+std::to_string(ans)<<std::endl;}
};void * RunThread(void * args)
{std:: string name;ThreadData * data = (ThreadData *)args;//std::cout <<"x:"<<data->x <<" y:"<<data->y<< std::endl;ThreadResult * result = new ThreadResult();result->ans = data->Excute();result->x = data->x;result->y = data->y;return (void*)result;
}
void PrintToHEX(pthread_t tid)
{char message[1024];snprintf(message,sizeof(message),"0x%lx",tid);std::cout<<message<<std::endl;
}
int main()
{pthread_t tid1;ThreadData * x1 = new ThreadData();x1->x = 10;x1->y = 20;pthread_create(&tid1, nullptr, RunThread,(void *)x1);ThreadResult * result =nullptr;int n = pthread_join(tid1,(void**)&result);result->print();if(n == 0){std::cout<<"wait success"<<std::endl;}return 0;
}
問題6: 如何創建多線程呢?
我們通過循環的方式 創建多線程哦~~
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
void * RunThread(void * args)
{std::string name = (const char *) args;std::cout<<name<<std::endl;return args;
}
int main()
{for(int i = 0; i < 10; i++){pthread_t tid;char name[1024];snprintf(name,sizeof(name),"this is %d new thread",i+1);pthread_create(&tid, nullptr , RunThread,name);}sleep(100);return 0;
}
我們發現結果并不符合預期。 因為name 只有一份,主線程 運行的時候不停在覆蓋 name。
我們用 new ,循環 new 十次 主線程分配的時候每一個線程都得到 一個地址,那個地址指向一個獨立的堆空間,線程之間就不互相影響了。
ok 完美解決~~!!!
問題7: 新線程如何終止?
1.我們可以在新線程中用return
2.我們可以在新線程中用pthread_exit 注意exit 是終止整個進程。
3.我們可以在新線程中使用pthrad_cancel
線程被取消線程的退出結果是:-1 #define PTHREAD_CANCELED ((void *) -1)
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>void *RunThread(void *args)
{while (true){std::string name = (const char *)args;std::cout << name << std::endl;sleep(3);}return args;
}int main()
{std::vector<pthread_t> tids;for (int i = 0; i < 10; i++){pthread_t tid;// char name[1024];char *name = new char[1024];snprintf(name, 1024, "this is %d new thread", i + 1);pthread_create(&tid, nullptr, RunThread, name);tids.push_back(tid);}for (auto tid : tids){// pthread_detach(tid);pthread_cancel(tid);void *ret = nullptr;int n = pthread_join(tid, &ret);std::cout << (long long int)ret << " quit.." << std::endl;// std::cout<<"n="<<n<<std::endl;}return 0;
}
pthread_cancel 是請求取消另一個線程,而 pthread_exit 是線程自身決定退出。
某個線程調用了exit 整個進程就結束了~
exit 你走錯片場了啊。要用pthread_exit
void * RunThread(void * args)
{std::string name = (const char *) args;std::cout<<name<<std::endl;pthread_exit(args);return args;
}
問題8: 可以不可以不join線程,讓他執行完就退出呢??可以!
我們用pthread_detach 就可以了,然后線程變成unjoinable 狀態,如果此時再等待就會出錯 然后終止整個進程。
#include <iostream>
#include <pthread.h>
#include <string>
#include <unistd.h>
#include <vector>void *RunThread(void *args)
{while (true){std::string name = (const char *)args;std::cout << name << std::endl;sleep(3);}return args;
}int main()
{std::vector<pthread_t> tids;for (int i = 0; i < 10; i++){pthread_t tid;// char name[1024];char *name = new char[1024];snprintf(name, 1024, "this is %d new thread", i + 1);pthread_create(&tid, nullptr, RunThread, name);tids.push_back(tid);}for (auto tid : tids){pthread_detach(tid); // 主線程分離新線程,新線程必須存在}for (auto tid : tids){// pthread_detach(tid);pthread_cancel(tid);void *ret = nullptr;int n = pthread_join(tid, &ret);std::cout << "n:"<<n<<std::endl;// std::cout<<"n="<<n<<std::endl;}return 0;
}
封裝線程
#include <string>
#include <pthread.h>
#include <functional>
namespace ThreadModel
{ class Thread{typedef void (*func_t)(std::string); // ?// using func_t = std::function<void()>;public:Thread(const std::string & name,func_t func):_name(name),_func(func){}~Thread(){}void Excute(){_isrunning = true;_func(_name); // 這里傳了線程名_isrunning = false;}static void * Routine(void * args) // 不用static 第一個參數是this{Thread * self = static_cast<Thread*>(args);// self->_func();self->Excute();return nullptr;}void start(){pthread_create(&_tid,nullptr,Routine,(void *)this);}void join(){// std::cout<<_isrunning<<std::endl;// std::cout<<true<<std::endl;if(_isrunning) // 只有在運行中的線程才需要被等待{//std::cout<<"..."<<std::endl;pthread_join(_tid,nullptr);}}// 終止線程void stop(){if(_isrunning){_isrunning = false;pthread_cancel(_tid);}}private:std::string _name;pthread_t _tid;func_t _func; bool _isrunning; // 主要 用于線程終止時 只有啟動了的線程 才能終止 };
}