Linux —— 線程控制
- 創建多個線程
- 線程的優缺點
- 優點
- 缺點
- pthread_self
- 進程和線程的關系
- pthread_exit
- 線程等待pthread_ join
- 線程的返回值
- 線程分離
- pthread_detach
- 線程取消
- pthread_cancel
- pthread_t 的理解
我們今天接著來學習線程:
創建多個線程
我們可以結合以前的知識,創建多個線程:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作為函數包裝器類型別名
using func_t = std::function<void()>;// 聲明線程池中線程的數量
const int thread_num = 5;// 定義一個結構體來保存線程的相關數據
class ThreadData {
public:// 構造函數初始化線程名稱、創建時間和函數執行體ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 線程名稱std::string thread_name;// 創建時間(以Unix時間戳表示)uint64_t create_time;// 線程執行的函數對象func_t func;
};// 線程處理函數,由pthread_create調用
void *Threadhandler(void *args) {// 強制類型轉換從void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);while (true) {// 打印線程信息并執行函數體std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// 模擬工作并讓線程休眠1秒sleep(1);}// 注意:此函數理論上不會返回,因為while(true),但C++要求非void*函數有返回值,這里返回nullptr以滿足編譯要求return nullptr;
}// 示例函數,供線程執行
void Print() {std::cout << "This is a process" << std::endl;
}int main() {// 用于存儲所有創建的線程ID的向量std::vector<pthread_t> threads;// 循環創建指定數量的線程for(int i = 0; i < thread_num; i++) {// 生成線程名稱std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 線程ID// 初始化每個線程的數據結構ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 創建線程,傳入處理函數和參數pthread_create(&tid, nullptr, Threadhandler, td);// 將創建的線程ID添加到向量中threads.push_back(tid);// 主線程休眠1秒,模擬間隔創建線程的效果sleep(1);}// 打印所有創建的線程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主線程可以在這里執行其他操作或等待,這里為了簡化直接結束// 實際應用中可能需要適當同步機制來管理這些線程的生命周期return 0;
}
我們也可以寫段代碼,監視我們的線程:
while :; do ps -aL | head -1 && ps -aL | grep mocess; sleep 1; done
線程的優缺點
線程(Threads)是現代操作系統中一種重要的并發執行機制,允許程序內部的多個控制流并發執行。以下是線程的一些主要優缺點:
優點
- 提高資源利用率:線程可以在同一個進程中共享內存和資源,減少了上下文切換的開銷,提高了系統的整體效率和資源利用率。
- 響應速度更快:多線程程序可以在等待某個任務(如I/O操作)完成的同時,處理其他任務,使得用戶界面更加流暢,響應速度更快。
- 簡化編程模型:對于一些復雜的并發任務,使用多線程可以簡化編程模型,使程序設計更加直觀。
- 靈活性和可擴展性:線程使得程序可以根據需要動態地分配工作,易于擴展以適應不同的負載需求。
- 并行處理:在多處理器或多核系統中,線程可以并行執行,充分利用硬件資源,顯著提升程序的執行效率。
缺點
- 資源共享和數據一致性問題:多個線程訪問共享資源可能導致競態條件、死鎖和資源爭用等問題,需要復雜的同步機制(如互斥鎖、信號量等)來保證數據的一致性,這會增加編程復雜度。
- 上下文切換開銷:盡管線程間上下文切換比進程快,但頻繁的線程切換仍然會消耗CPU時間,降低性能。
- 內存和資源占用:每個線程都會占用一定的內存空間(如棧空間),大量線程會導致內存消耗增加,特別是在內存受限的環境中。
- 調試困難:多線程程序的調試相對單線程程序更為復雜,因為問題可能與線程的執行順序有關,難以復現和定位錯誤。
- 死鎖和活鎖:不當的線程同步可能導致死鎖,即兩個或更多的線程互相等待對方釋放資源而無法繼續執行。此外,活鎖也是可能的問題,線程持續進行無意義的操作等待某種條件發生,但實際上條件永遠無法達成。
同時,線程的健壯性并不是很優秀:
線程的健壯性相比多進程來說通常被認為較低。這是因為線程共享同一進程的內存空間和資源,這種資源共享的特性帶來了以下幾點關于健壯性的影響:
- 資源共享風險:線程之間可以直接訪問共享內存,包括全局變量和其他靜態數據,這可能導致數據競爭和競態條件。如果不采取合適的同步措施(如互斥鎖、信號量等),一個線程對共享數據的修改可能會干擾其他線程,從而引發不可預測的行為,甚至程序崩潰。
- 異常傳播:在某些情況下,一個線程的異常終止(如 segmentation fault)可能會直接影響到整個進程,導致所有線程一起終止。這是因為所有線程共享同一地址空間,一個線程的錯誤操作可能破壞其他線程正在使用的數據結構或資源。
- 線程安全問題:編寫線程安全的代碼需要額外的努力,比如正確管理鎖的使用、避免死鎖和活鎖等,這增加了開發的復雜度。如果線程間的交互沒有正確處理,很容易引入難以發現和修復的錯誤。
- 調試挑戰:多線程程序的調試比單線程程序更為復雜。由于線程執行的并發性和不確定性,錯誤可能不會穩定復現,且問題的原因可能隱藏在復雜的線程交互中,這使得調試和故障排查變得困難。
我們舉個例子,我們故意觸發異常:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作為函數包裝器類型別名
using func_t = std::function<void()>;// 聲明線程池中線程的數量
const int thread_num = 5;// 定義一個結構體來保存線程的相關數據
class ThreadData {
public:// 構造函數初始化線程名稱、創建時間和函數執行體ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 線程名稱std::string thread_name;// 創建時間(以Unix時間戳表示)uint64_t create_time;// 線程執行的函數對象func_t func;
};// 線程處理函數,由pthread_create調用
void *Threadhandler(void *args) {int a = 10;// 強制類型轉換從void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);while (true) {// 打印線程信息并執行函數體std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();if(td->thread_name == "thread 1"){std::cout << td->thread_name << " alarm !!!!!" << std::endl;a /= 0; // 故意制作異常}// 模擬工作并讓線程休眠1秒sleep(1);}// 注意:此函數理論上不會返回,因為while(true),但C++要求非void*函數有返回值,這里返回nullptr以滿足編譯要求return nullptr;
}// 示例函數,供線程執行
void Print() {std::cout << "This is a process" << std::endl;
}int main() {// 用于存儲所有創建的線程ID的向量std::vector<pthread_t> threads;// 循環創建指定數量的線程for(int i = 0; i < thread_num; i++) {// 生成線程名稱std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 線程ID// 初始化每個線程的數據結構ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 創建線程,傳入處理函數和參數pthread_create(&tid, nullptr, Threadhandler, td);// 將創建的線程ID添加到向量中threads.push_back(tid);// 主線程休眠1秒,模擬間隔創建線程的效果sleep(1);}// 打印所有創建的線程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主線程可以在這里執行其他操作或等待,這里為了簡化直接結束// 實際應用中可能需要適當同步機制來管理這些線程的生命周期return 0;
}
我們這里1號線程觸發異常,整個進程直接掛掉。
因此,雖然多線程可以提高程序的效率和響應性,但其健壯性依賴于開發者對并發控制和資源管理的精細設計。實踐中,通常推薦使用高級并發工具、遵循最佳實踐,并進行充分的測試來確保線程安全和提高程序的健壯性。
pthread_self
pthread_self可以獲取自身的線程id:
進程和線程的關系
一般來說,線程和進程的關系是,牽一發而動全身,比如:一個線程退出,不能直接exit退出:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作為函數包裝器類型別名
using func_t = std::function<void()>;// 聲明線程池中線程的數量
const int thread_num = 5;// 定義一個結構體來保存線程的相關數據
class ThreadData
{
public:// 構造函數初始化線程名稱、創建時間和函數執行體ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 線程名稱std::string thread_name;// 創建時間(以Unix時間戳表示)uint64_t create_time;// 線程執行的函數對象func_t func;
};// 線程處理函數,由pthread_create調用
void *Threadhandler(void *args)
{int a = 10;// 強制類型轉換從void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);// 打印線程信息并執行函數體std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// if(td->thread_name == "thread 1")// {// std::cout << td->thread_name << " alarm !!!!!" << std::endl;// a /= 0; // 故意制作異常// }// 模擬工作并讓線程休眠1秒sleep(1);// 注意:此函數理論上不會返回,因為while(true),但C++要求非void*函數有返回值,這里返回nullptr以滿足編譯要求exit(0); //exit返回//return nullptr;
}// 示例函數,供線程執行
void Print()
{std::cout << "This is a process" << std::endl;
}int main() {// 用于存儲所有創建的線程ID的向量std::vector<pthread_t> threads;// 循環創建指定數量的線程for(int i = 0; i < thread_num; i++) {// 生成線程名稱std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 線程ID// 初始化每個線程的數據結構ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 創建線程,傳入處理函數和參數pthread_create(&tid, nullptr, Threadhandler, td);// 將創建的線程ID添加到向量中threads.push_back(tid);// 主線程休眠1秒,模擬間隔創建線程的效果sleep(1);}// 打印所有創建的線程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主線程可以在這里執行其他操作或等待,這里為了簡化直接結束// 實際應用中可能需要適當同步機制來管理這些線程的生命周期return 0;
}
我們這里創建了5個線程,但是線程1 exit會直接導致整個進程退出,所以,如何優雅的實現線程的退出呢?一般返回nullptr即可:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作為函數包裝器類型別名
using func_t = std::function<void()>;// 聲明線程池中線程的數量
const int thread_num = 5;// 定義一個結構體來保存線程的相關數據
class ThreadData
{
public:// 構造函數初始化線程名稱、創建時間和函數執行體ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 線程名稱std::string thread_name;// 創建時間(以Unix時間戳表示)uint64_t create_time;// 線程執行的函數對象func_t func;
};// 線程處理函數,由pthread_create調用
void *Threadhandler(void *args)
{int a = 10;// 強制類型轉換從void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);// 打印線程信息并執行函數體std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// if(td->thread_name == "thread 1")// {// std::cout << td->thread_name << " alarm !!!!!" << std::endl;// a /= 0; // 故意制作異常// }// 模擬工作并讓線程休眠1秒sleep(1);// 注意:此函數理論上不會返回,因為while(true),但C++要求非void*函數有返回值,這里返回nullptr以滿足編譯要求//exit(0); //exit返回return nullptr;
}// 示例函數,供線程執行
void Print()
{std::cout << "This is a process" << std::endl;
}int main() {// 用于存儲所有創建的線程ID的向量std::vector<pthread_t> threads;// 循環創建指定數量的線程for(int i = 0; i < thread_num; i++) {// 生成線程名稱std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 線程ID// 初始化每個線程的數據結構ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 創建線程,傳入處理函數和參數pthread_create(&tid, nullptr, Threadhandler, td);// 將創建的線程ID添加到向量中threads.push_back(tid);// 主線程休眠1秒,模擬間隔創建線程的效果sleep(1);}// 打印所有創建的線程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主線程可以在這里執行其他操作或等待,這里為了簡化直接結束// 實際應用中可能需要適當同步機制來管理這些線程的生命周期return 0;
}
同時,我們還可以用pthread_exit:
pthread_exit
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>// 使用std::function作為函數包裝器類型別名
using func_t = std::function<void()>;// 聲明線程池中線程的數量
const int thread_num = 5;// 定義一個結構體來保存線程的相關數據
class ThreadData
{
public:// 構造函數初始化線程名稱、創建時間和函數執行體ThreadData(const std::string &name, const uint64_t &ctime, func_t f): thread_name(name), create_time(ctime), func(f) {}// 線程名稱std::string thread_name;// 創建時間(以Unix時間戳表示)uint64_t create_time;// 線程執行的函數對象func_t func;
};// 線程處理函數,由pthread_create調用
void *Threadhandler(void *args)
{int a = 10;// 強制類型轉換從void*到ThreadData*ThreadData *td = static_cast<ThreadData *>(args);// 打印線程信息并執行函數體std::cout << "new thread thread name: " << td->thread_name << " create time: " << td->create_time << std::endl;td->func();// if(td->thread_name == "thread 1")// {// std::cout << td->thread_name << " alarm !!!!!" << std::endl;// a /= 0; // 故意制作異常// }// 模擬工作并讓線程休眠1秒sleep(1);// 注意:此函數理論上不會返回,因為while(true),但C++要求非void*函數有返回值,這里返回nullptr以滿足編譯要求//exit(0); //exit返回//return nullptr;pthread_exit(nullptr);
}// 示例函數,供線程執行
void Print()
{std::cout << "This is a process" << std::endl;
}int main() {// 用于存儲所有創建的線程ID的向量std::vector<pthread_t> threads;// 循環創建指定數量的線程for(int i = 0; i < thread_num; i++) {// 生成線程名稱std::string name = "thread " + std::to_string(i + 1);pthread_t tid; // 線程ID// 初始化每個線程的數據結構ThreadData *td = new ThreadData(name, (uint64_t)time(nullptr), Print);// 創建線程,傳入處理函數和參數pthread_create(&tid, nullptr, Threadhandler, td);// 將創建的線程ID添加到向量中threads.push_back(tid);// 主線程休眠1秒,模擬間隔創建線程的效果sleep(1);}// 打印所有創建的線程IDstd::cout << "thread ids: ";for(const auto &tid: threads) {std::cout << tid << ",";}std::cout << std::endl;// 主線程可以在這里執行其他操作或等待,這里為了簡化直接結束// 實際應用中可能需要適當同步機制來管理這些線程的生命周期return 0;
}
線程等待pthread_ join
線程既然是進程的迷你版,肯定也會有跟進程相關的地方,比如我們的線程退出時也是需要被等待的,而我們所用的接口就是就是pthread_join:
我們來舉個例子:
#include <iostream> // 標準輸入輸出庫
#include <unistd.h> // 定義了 usleep 函數,用于延遲線程執行
#include <cstring> // 字符串操作函數庫
#include <vector> // 動態數組容器
#include <functional> // 函數對象包裝器庫
#include <time.h> // 時間相關函數庫
#include <pthread.h> // POSIX 線程庫// 線程處理函數
void *Threadhandler(void *args)
{usleep(1000); // 線程啟動后暫停1毫秒// 將傳入的void指針轉換為std::string類型,作為線程名稱std::string name = static_cast<const char*>(args);int cnt = 5; // 循環計數器// 輸出線程信息并等待一秒,循環5次while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl; // 打印當前線程的輕量級進程ID(LWP)sleep(1); // 線程休眠1秒}return nullptr; // 線程結束,返回空指針
}int main()
{pthread_t tid; // 定義線程ID變量// 創建新線程,傳入線程處理函數、線程屬性(nullptr表示使用默認屬性)、入口參數和線程ID的地址pthread_create(&tid, nullptr, Threadhandler, (void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl; // 打印主線程的LWPsleep(10); // 主線程休眠10秒,保證子線程有足夠時間運行// 等待子線程tid結束,成功返回0,失敗返回非零值。第二個參數接收線程的返回值,這里不需要所以傳入nullptrint n = pthread_join(tid, nullptr);std::cout << "return value is :" << n << std::endl; // 打印pthread_join的返回值,表示是否成功加入線程return 0; // 主程序結束
}
這里注意一下,如果線程退出并沒有被等待,會導致類似像僵尸進程這樣的問題,但是這個不怎么容易觀察。
線程的返回值
我們來看看pthread_join的手冊:
后面這個retval這個參數,在線程退出的時候,會把退出的結果放到retval中,意思就是這個retval是一個輸出型參數,輸入之后會把線程的退出結果帶出來:
我們可以試一試:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <vector>
#include <functional>
#include <time.h>
#include <pthread.h>void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}return (void *)"thread-1";
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;void *ret = nullptr;int n = pthread_join(tid,&ret);std::cout << "return value is :" << (const char*)ret << std::endl;return 0;
}
我們這里返回的是一個字符串,我們看看我們能否打印的出來:
這里用pthread_exit也是可以的。
這里既然是void *說明我們是可以傳遞任何類型,我們也可以傳遞一個結構體回去:
#include <iostream>
#include <unistd.h>
#include <cstring>
#include <string>// 自定義線程返回結構體,用于存儲線程退出時的相關信息
class ThreadReturn
{
public:// 構造函數,初始化線程ID、信息和退出碼ThreadReturn(pthread_t id, std::string info, int code): _id(id), // 線程ID_info(info), // 線程退出時的信息_code(code) // 線程退出碼{}// 數據成員pthread_t _id; // 線程的ID,在線程退出時記錄std::string _info; // 線程退出時的描述信息int _code; // 線程退出的狀態碼
};// 線程處理函數
void *Threadhandler(void *args)
{usleep(1000); // 暫停1毫秒std::string name = static_cast<const char*>(args); // 獲取線程名稱int cnt = 5; // 循環計數器// 循環輸出線程信息并休眠,模擬工作過程while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}// 線程處理完畢,創建ThreadReturn實例以傳遞退出信息ThreadReturn* ret = new ThreadReturn(pthread_self(), name, 10);return ret; // 將ThreadReturn對象的地址作為線程的返回值
}int main()
{pthread_t tid; // 主線程中定義線程ID// 創建新線程,傳入處理函數、線程屬性、參數和線程ID指針pthread_create(&tid, nullptr, Threadhandler, (void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl; // 輸出主線程IDvoid *ret = nullptr; // 定義一個void指針來接收線程的返回值int n = pthread_join(tid, &ret); // 等待線程tid結束,并獲取其返回值到ret指針// 將ret指針轉換為ThreadReturn對象指針,以便訪問其中的數據ThreadReturn* r = static_cast<ThreadReturn*>(ret);if(r != nullptr) { // 確保轉換成功std::cout << "return value is :" << "id :" << r->_id << std::endl;std::cout << "return value is :" << "info :" << r->_info << std::endl;std::cout << "return value is :" << "code :" << r->_code << std::endl;delete r; // 釋放分配的內存}return 0; // 主程序結束
}
線程分離
我們之前通過實驗看到了線程和線程之間的關聯,線程退出之后要進行回收。
但其實,如果我們的主線程只想完成自己的任務,而并不想管其他的線程可不可以呢?答案是可以的,我們可以進行線程分離,使之主線程不管其他線程的死活:
pthread_detach
pthread_detach()
是POSIX線程庫中的一個函數,用于改變指定線程的分離狀態。當一個線程被“分離”(detached)時,它會在執行結束后自動被系統回收資源,而不需要其他線程調用pthread_join()
來顯式等待它結束。這對于那些不需要收集線程返回值或者不需要精確控制線程結束時間的場景非常有用。
函數原型如下:
int pthread_detach(pthread_t thread);
- 參數:
thread
:要分離的線程的標識符(ID),即之前通過pthread_create()
創建線程時返回的值。
- 返回值:
- 成功時返回0。
- 失敗時返回非零的錯誤碼。
功能:
- 如果調用成功,指定的線程將在終止時自動釋放其資源,包括棧和線程描述符,而不需要其他線程的進一步干預。
- 分離狀態的線程不能被其他線程通過
pthread_join()
來等待和回收資源。
使用場景:
- 當線程執行的任務是獨立的,不需要與其他線程同步或交換數據時。
- 當你不需要關心線程的具體結束狀態或返回值,只關心它執行完成即可。
注意事項:
- 一旦線程被分離,就不能再通過
pthread_join()
來等待它。- 如果你既想在線程結束時執行某些清理工作,又想讓它自動回收資源,可以在線程函數的最后手動執行清理操作,然后再調用
pthread_exit()
顯式終止線程,這樣分離后也能確保資源被正確回收。- 調用
pthread_detach()
前應確保線程還在運行,不要對尚未創建或已經終止的線程調用此函數。
void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");pthread_detach(tid);std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;void *ret = nullptr;int n = pthread_join(tid,&ret);std::cout << "return value is :" << "code :"<< (long long int)n << std::endl;return 0;
}
我們的錯誤碼設置為了22,表示一個無效的輸入:
錯誤碼22通常對應于EINVAL錯誤,即無效參數(Invalid argument)。這意味著傳遞給pthread_join的線程ID無效,或者線程已經終止并且不是加入(joinable)狀態。由于線程已經被顯式分離(通過pthread_detach(pthread_self())),它不再是可加入狀態,因此嘗試用pthread_join來等待這個線程就會收到EINVAL錯誤。
線程取消
pthread_cancel
pthread_cancel()
是POSIX線程庫中的一個函數,用于請求取消(cancellation)指定的線程。這意味著請求線程(調用pthread_cancel
的線程)向目標線程發送一個取消請求,目標線程在接收到這個請求后,根據其取消狀態和取消類型,可以選擇立即終止或在某個合適的時機終止執行。
函數原型如下:
int pthread_cancel(pthread_t thread);
- 參數:
thread
:要被取消的線程的標識符(ID),即之前通過pthread_create()
創建線程時返回的值。
- 返回值:
- 成功時返回0。
- 失敗時返回非零的錯誤碼,如
ESRCH
(沒有這樣的線程)。
功能:
- 發送一個取消請求給指定的線程。目標線程是否響應這個請求取決于其取消狀態和取消類型。
- 線程默認是不響應取消的(即取消點的插入是可選的),這意味著僅發出取消請求并不會立即停止線程,除非線程在某個取消點上主動檢查取消請求狀態。
- 為了使線程能夠響應取消,開發者需要在代碼中適當的位置插入取消點,通常是通過調用某些庫函數(如
sleep()
,pthread_testcancel()
)自動完成的,或者顯式地調用pthread_testcancel()
。
使用場景:
- 當需要基于外部條件(如用戶中斷、錯誤處理等)提前結束線程的執行時。
- 在長任務中提供取消機制,增強程序的靈活性和響應性。
注意事項:
- 線程可以在接到取消請求后執行清理工作,通過設置取消類型(
PTHREAD_CANCEL_DEFERRED
或PTHREAD_CANCEL_ASYNCHRONOUS
)來控制響應方式。- 即使取消請求被發送,線程也可能不會立即終止,除非它正在執行取消點或已經設置了立即響應取消的類型。
- 取消線程應謹慎使用,特別是當線程持有鎖或資源時,突然取消可能導致資源泄露或其他一致性問題,需要確保資源正確清理。
- 成功發送取消請求并不意味著線程已經終止,需要通過其他機制(如共享變量、條件變量等)來確認線程是否已響應取消并完成清理。
比如我們可以這樣:
void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}//pthread_detach(pthread_self());return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;//pthread_detach(tid);int n = pthread_cancel(tid);std::cout << "cancel value :" << strerror((int64_t)n) << std::endl;void *ret = nullptr;n = pthread_join(tid,&ret);std::cout << "return value is :" << "code :"<< strerror((long long int)n) << std::endl;return 0;
}
我們看到取消和等待都是成功了的,但是如果我們線程分離了呢?
void *Threadhandler(void *args)
{usleep(1000);std::string name = static_cast<const char*>(args);int cnt = 5;while(cnt--){std::cout << "I am a Light process My LWP is :" << pthread_self() << std::endl;sleep(1);}pthread_detach(pthread_self());return nullptr;
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,Threadhandler,(void *)"thread - 1");std::cout << "I am the main Light process My LWP is :" << pthread_self() << std::endl;pthread_detach(tid);int n = pthread_cancel(tid);std::cout << "cancel value :" << strerror((int64_t)n) << std::endl;void *ret = nullptr;n = pthread_join(tid,&ret);std::cout << "return value is :" << "code :"<< strerror((long long int)n) << std::endl;return 0;
}
我們發現,線程分離之后,可以完成線程取消,但是不能完成線程等待。
pthread_t 的理解
我們之前打印過pthread_t 的編號:
這串數字換成十六進制,其實就是一個地址,每一個線程都有自己的地址。
那么這個地址到底是什么呢?
我們首先知道,Linux在系統上并沒有提供關于線程的接口,管理線程的是它原生的pthread庫:
那么,我們每創建一個線程,庫都要組織管理它,所以最后庫中就會存儲的有每個線程的結構地址:
而我們打印的那一大串數字就是每個struct_pthread 的地址。