Linux —— 線程控制

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)是現代操作系統中一種重要的并發執行機制,允許程序內部的多個控制流并發執行。以下是線程的一些主要優缺點:

優點

  1. 提高資源利用率:線程可以在同一個進程中共享內存和資源,減少了上下文切換的開銷,提高了系統的整體效率和資源利用率。
  2. 響應速度更快:多線程程序可以在等待某個任務(如I/O操作)完成的同時,處理其他任務,使得用戶界面更加流暢,響應速度更快。
  3. 簡化編程模型:對于一些復雜的并發任務,使用多線程可以簡化編程模型,使程序設計更加直觀。
  4. 靈活性和可擴展性:線程使得程序可以根據需要動態地分配工作,易于擴展以適應不同的負載需求。
  5. 并行處理:在多處理器或多核系統中,線程可以并行執行,充分利用硬件資源,顯著提升程序的執行效率。

缺點

  1. 資源共享和數據一致性問題:多個線程訪問共享資源可能導致競態條件、死鎖和資源爭用等問題,需要復雜的同步機制(如互斥鎖、信號量等)來保證數據的一致性,這會增加編程復雜度。
  2. 上下文切換開銷:盡管線程間上下文切換比進程快,但頻繁的線程切換仍然會消耗CPU時間,降低性能。
  3. 內存和資源占用:每個線程都會占用一定的內存空間(如棧空間),大量線程會導致內存消耗增加,特別是在內存受限的環境中。
  4. 調試困難:多線程程序的調試相對單線程程序更為復雜,因為問題可能與線程的執行順序有關,難以復現和定位錯誤。
  5. 死鎖和活鎖:不當的線程同步可能導致死鎖,即兩個或更多的線程互相等待對方釋放資源而無法繼續執行。此外,活鎖也是可能的問題,線程持續進行無意義的操作等待某種條件發生,但實際上條件永遠無法達成。

同時,線程的健壯性并不是很優秀:

線程的健壯性相比多進程來說通常被認為較低。這是因為線程共享同一進程的內存空間和資源,這種資源共享的特性帶來了以下幾點關于健壯性的影響:

  1. 資源共享風險:線程之間可以直接訪問共享內存,包括全局變量和其他靜態數據,這可能導致數據競爭和競態條件。如果不采取合適的同步措施(如互斥鎖、信號量等),一個線程對共享數據的修改可能會干擾其他線程,從而引發不可預測的行為,甚至程序崩潰。
  2. 異常傳播:在某些情況下,一個線程的異常終止(如 segmentation fault)可能會直接影響到整個進程,導致所有線程一起終止。這是因為所有線程共享同一地址空間,一個線程的錯誤操作可能破壞其他線程正在使用的數據結構或資源。
  3. 線程安全問題:編寫線程安全的代碼需要額外的努力,比如正確管理鎖的使用、避免死鎖和活鎖等,這增加了開發的復雜度。如果線程間的交互沒有正確處理,很容易引入難以發現和修復的錯誤。
  4. 調試挑戰:多線程程序的調試比單線程程序更為復雜。由于線程執行的并發性和不確定性,錯誤可能不會穩定復現,且問題的原因可能隱藏在復雜的線程交互中,這使得調試和故障排查變得困難。

我們舉個例子,我們故意觸發異常:

#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_DEFERREDPTHREAD_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 的地址。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/12023.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/12023.shtml
英文地址,請注明出處:http://en.pswp.cn/web/12023.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【離散數學】偏序關系中蓋住關系的求取及格論中有補格的判定(c語言實現)

實驗要求 求n的因子函數 我們將n的因子存入數組中&#xff0c;n的因子就是可以整除n的數&#xff0c;所以我們通過一個for循環來求。返回因子個數。 //求n的因子,返回因子個數 int factors(int arr[], int n) {int j 0;for (int i 1; i < n; i){if (n % i 0){arr[j] i…

C++反向迭代器

C反向迭代器 反向迭代器是用正向迭代器適配實現的&#xff0c;本質是寫一個反向迭代器的類模板&#xff0c;給編譯器傳不同的容器的正向迭代器實例化&#xff0c;編譯器去實例化出各種類模板對應的反向迭代器。 #pragma once namespace my_reverse_iterator {template<cla…

代碼隨想錄算法訓練營第五十三天| 1143.最長公共子序列,1035.不相交的線,53. 最大子序和

目錄 題目鏈接&#xff1a;1143.最長公共子序列 思路 代碼 題目鏈接&#xff1a; 1035.不相交的線 思路 代碼 題目鏈接&#xff1a; 53. 最大子序和 思路 代碼 總結 題目鏈接&#xff1a;1143.最長公共子序列 思路 ①dp數組&#xff0c;dp[i][j]表示[0,i-1]的text1和…

軟件測試面試78問

&#x1f345; 視頻學習&#xff1a;文末有免費的配套視頻可觀看 &#x1f345; 點擊文末小卡片 &#xff0c;免費獲取軟件測試全套資料&#xff0c;資料在手&#xff0c;漲薪更快 1、問&#xff1a;你在測試中發現了一個bug&#xff0c;但是開發經理認為這不是一個bug&#xf…

關于使用git拉取gitlab倉庫的步驟(解決公鑰問題和pytho版本和repo版本不對應的問題)

先獲取權限&#xff0c;提交ssh-key 虛擬機連接 GitLab并提交代碼_gitlab提交mr-CSDN博客 配置完成上訴步驟之后&#xff0c;執行下列指令進行拉去倉庫的內容 sudo apt install repo export PATHpwd/.repo/repo:$PATH python3 "實際路徑"/repo init -u ssh://gitxx…

智能優化算法 | Matlab實現成長優化算法(Growth Optimizer,GO)(內含完整源碼)

智能優化算法 | Matlab實現成長優化算法(Growth Optimizer,GO)(內含完整源碼) 文章目錄 智能優化算法 | Matlab實現成長優化算法(Growth Optimizer,GO)(內含完整源碼)文章概述源碼設計文章概述 智能優化算法 | Matlab實現成長優化算法(Growth Optimizer,GO)(內含…

Java的類和對象(一)—— 初始類和對象,this關鍵字,構造方法

前言 從這篇文章開始&#xff0c;我們就進入到了JavaSE的核心部分。這篇文章是Java類和對象的第一篇&#xff0c;主要介紹類和對象的概念&#xff0c;this關鍵字以及構造方法~~ 什么是類&#xff1f;什么是對象&#xff1f; 學過C語言的老鐵們&#xff0c;可以類比struct自定義…

【哈希】Leetcode 383. 贖金信【簡單】

贖金信 給你兩個字符串&#xff1a;ransomNote 和 magazine &#xff0c;判斷 ransomNote 能不能由 magazine 里面的字符構成。 如果可以&#xff0c;返回 true &#xff1b;否則返回 false 。 magazine 中的每個字符只能在 ransomNote 中使用一次。 解題思路 可以使用哈希…

matlab進行濾波處理

在MATLAB中進行濾波處理&#xff0c;你可以使用內置的函數或自定義濾波器。以下是一些常見的方法&#xff1a; 1. 使用內置濾波器函數 MATLAB提供了多種內置濾波器函數&#xff0c;如filter&#xff0c;filtfilt&#xff0c;butter&#xff08;用于設計巴特沃斯濾波器&#x…

spark結課之tip2

spark常用方法總結&#xff1a; 一、從內部創建RDD (1).通過并行化集合&#xff08;Parallelized Collections&#xff09;&#xff1a; 可以使用SparkContext的parallelize方法將一個已有的集合轉換為RDD。 基本語法&#xff1a; parallelize(collection, numSlicesNone)…

AI系列:大語言模型的RAG(檢索增強生成)技術(下)-- 使用LlamaIndex

目錄 前言什么是LlamaIndex?LlamaIndex代碼設置embedding模型設置LLM模型索引查詢機 驗證使用感受參考資料 前言 繼上一篇文章AI系列&#xff1a;大語言模型的RAG&#xff08;檢索增強生成&#xff09;技術&#xff08;上&#xff09;&#xff0c;這篇文章主要以LlamaIndex為…

銀行業數據運營場景下的數據埋點方案

1、引言 隨著金融科技的快速發展&#xff0c;銀行業的數據運營變得日益重要。數據埋點作為數據收集的重要手段&#xff0c;對于銀行業務的精細化運營、風險管理和產品迭代等方面起著至關重要的作用。本方案將針對銀行業數據運營場景&#xff0c;設計一套完整的數據埋點方案&am…

【生信技能樹】GEO數據挖掘全流程

R包的安裝&#xff0c;每次做分析的時候先運行這段代碼把R包都安裝好了&#xff0c;這段代碼不需要任何改動&#xff0c;每次分析直接運行。 options("repos""https://mirrors.ustc.edu.cn/CRAN/") if(!require("BiocManager")) install.packag…

思源筆記如何結合群暉WebDav實現云同步數據

文章目錄 1. 開啟群暉WebDav 服務2. 本地局域網IP同步測試3. 群暉安裝Cpolar4. 配置遠程同步地址5. 筆記遠程同步測試6. 固定公網地址7. 配置固定遠程同步地址 在數字化時代&#xff0c;信息的同步與共享變得尤為重要。無論是個人用戶還是企業團隊&#xff0c;都渴望能夠實現跨…

nginx 代理java 請求報502

情況&#xff1a;nginx代理java 請求 后端返回正常&#xff0c;但是經過nginx 時報502 經過多次對比其他接口發現可能是返回的請求頭過大&#xff0c;導致nginx 報錯&#xff1a;如下 2024/05/13 02:57:12 [error] 88#88: *3755 upstream sent too big header while reading r…

創建存儲過程

一、DDL與DML CREATE TABLE student (id INT PRIMARY KEY AUTO_INCREMENT,createDate DATETIME NOT NULL,userName VARCHAR(255) NOT NULL,phone VARCHAR(20) NOT NULL,age INT NOT NULL,sex ENUM(男, 女) NOT NULL,introduce TEXT ); INSERT INTO student (createDate, userN…

透明加密軟件推薦:哪款實用又高效?

透明加密軟件是一種專門針對文件保密需求的計算機加密工具。 其核心在于“透明”二字&#xff0c;意味著整個加密過程對于使用者來說是無形且無感知的。 當用戶進行文件的日常操作&#xff0c;如打開、編輯或保存時&#xff0c;透明加密軟件會在后臺自動進行加密和解密工作&a…

【算法刷題day52】Leetcode:300. 最長遞增子序列、674. 最長連續遞增序列、718. 最長重復子數組

文章目錄 Leetcode 300. 最長遞增子序列解題思路代碼總結 Leetcode 674. 最長連續遞增序列解題思路代碼總結 Leetcode 718. 最長重復子數組解題思路代碼總結 草稿圖網站 java的Deque Leetcode 300. 最長遞增子序列 題目&#xff1a;300. 最長遞增子序列 解析&#xff1a;代碼隨…

Keil編程不同驅動文件引用同一個常量的處理方法

基礎不牢&#xff0c;地動山搖&#xff0c;最近單片機編程又遇到一個基礎問題。 我在頭文件中定義了一個常量同時給兩個驅動文件使用&#xff0c;封裝的時候編譯沒問題&#xff0c;但是在main函數中引用驅動函數的時候就出現了重定義的問題&#xff0c;如下如所示。 解決方法很…