目錄
(一)POSIX線程庫
(二)創建線程
2.1?線程ID及進程地址空間布局
(三)線程終止
(四)分離線程
(一)POSIX線程庫
POSIX線程庫(POSIX Threads Library),通常簡稱為Pthreads,是一個為POSIX操作系統(包括UNIX和類UNIX系統如Linux)提供線程支持的庫。Pthreads定義了一組API,允許程序員創建、管理、同步和銷毀線程。?
- 與線程有關的函數構成了一個完整的系列,絕大多數函數的名字都是以“pthread_”打頭的
- 要使用這些函數庫,要通過引入頭文<pthread.h>
- 鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項
(二)創建線程
【函數介紹】?
功能:創建一個新的線程原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (*start_routine)(void*), void *arg);參數thread:返回線程IDattr:設置線程的屬性,attr為NULL表示使用默認屬性start_routine:是個函數地址,線程啟動后要執行的函數arg:傳給線程啟動函數的參數返回值:成功返回0;失敗返回錯誤碼
錯誤檢查:
-
對于Pthreads函數,如果調用失敗,通常會返回一個非零的錯誤代碼,你可以使用這個錯誤代碼來查詢具體是什么錯誤發生了。為了將錯誤代碼映射到人類可讀的錯誤消息,你可以使用
strerror
或pthread_strerror
函數(如果可用)。 -
另外,Pthreads庫確實為每個線程提供了一個私有的errno變量,這允許線程安全地訪問errno。然而,對于Pthreads函數本身來說,直接檢查返回值通常是更好的做法,因為這樣可以避免任何可能的線程間干擾,并且通常更加高效。
?【代碼示例】?
#include <iostream>
#include <unistd.h>
#include <pthread.h>using namespace std;int g_val = 0; void *threadRun1(void *args)
{while (true){sleep(1);cout << "t1 thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val << endl;}
}void *threadRun2(void *args)
{while (true){sleep(1);cout << "t2 thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val++ << endl;}
}int main()
{pthread_t t1, t2;pthread_create(&t1, nullptr, threadRun1, nullptr);pthread_create(&t1, nullptr, threadRun2, nullptr);while (true){sleep(1);cout << "main thread..." << getpid() << " &g_val: " << &g_val << " , g_val: " << g_val << endl;}
}
2.1?線程ID及進程地址空間布局
- pthread_ create函數會產生一個線程ID,存放在第一個參數指向的地址中。該線程ID和前面說的線程ID不是一回事。
- 前面講的線程ID屬于進程調度的范疇。因為線程是輕量級進程,是操作系統調度器的最小單位,所以需要一個數值來唯一表示該線程。
- pthread_ create函數第一個參數指向一個虛擬內存單元,該內存單元的地址即為新創建線程的線程ID,屬于NPTL線程庫的范疇。線程庫的后續操作,就是根據該線程ID來操作線程的。
- 線程庫NPTL提供了pthread_ self函數,可以獲得線程自身的ID:
【函數介紹】?
pthread_t pthread_self(void);
錯誤檢查:
- 如果成功,
pthread_self
?返回調用線程的線程 ID。如果發生錯誤,這個函數沒有定義錯誤碼返回機制,因為它總是應該成功。
??【代碼示例】?
#include <iostream>
#include <pthread.h>
#include <unistd.h>using namespace std;void* threadRun(void* arg) { pthread_t id = pthread_self(); // 獲取當前線程的 ID cout << "the thread ID is : " << id << endl; sleep(1); return nullptr;
} int main() { pthread_t tid; int ret; // 創建線程 ret = pthread_create(&tid, nullptr, threadRun, nullptr); if (ret != 0) { cerr << "Error: pthread_create() failed with error " << ret << endl; return 1; } // 等待線程完成 pthread_join(tid, nullptr); cout << "Main thread exiting." << endl; return 0;
}
?pthread_t 到底是什么類型呢?取決于實現。對于Linux目前實現的NPTL實現而言,pthread_t類型的線程ID,本質就是一個進程地址空間上的一個地址。
(三)線程終止
- 1. 從線程函數return。這種方法對主線程不適用,從main函數return相當于調用exit。
#define NUM 10void *threadrun(void *args)
{char *name = (char*)args;while (true){cout << "new thread run, the new thread name is : " << name << endl;sleep(3);break;}delete name;return nullptr;
}int main()
{pthread_t tids[NUM];for(int i= 0; i< NUM; ++i){char *tname = new char[64];snprintf(tname,64,"thread-%d",i+1);pthread_create(tids+i,nullptr,threadrun,tname);}void *ret = nullptr;for(int i= 0; i< NUM; ++i){int n = pthread_join(tids[i],nullptr);if(n != 0) cerr << "pthread_join error" << endl;cout << "thread quit: " <<(uint64_t)ret << endl;}cout << "all thread quit..."<<endl; return 0;
}
- 2. 線程可以調用pthread_ exit終止自己。
【函數介紹】?
功能:線程終止
原型void pthread_exit(void *value_ptr);
參數value_ptr:value_ptr不要指向一個局部變量。返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)
- 需要注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。
?【代碼示例】?
#define NUM 10void *threadrun(void *args)
{char *name = (char*)args;while (true){cout << "new thread run, the new thread name is : " << name << endl;sleep(3);break;}delete name;pthread_exit((void*)1);
}int main()
{pthread_t tids[NUM];for(int i= 0; i< NUM; ++i){char *tname = new char[64];snprintf(tname,64,"thread-%d",i+1);pthread_create(tids+i,nullptr,threadrun,tname);}void *ret = nullptr;for(int i= 0; i< NUM; ++i){int n = pthread_join(tids[i],nullptr);if(n != 0) cerr << "pthread_join error" << endl;cout << "thread quit: " <<(uint64_t)ret << endl;}cout << "all thread quit..."<<endl; return 0;
}
- 3. 一個線程可以調用pthread_ cancel終止同一進程中的另一個線程。
【函數介紹】?
功能:取消一個執行中的線程
原型int pthread_cancel(pthread_t thread);
參數thread:線程ID返回值:成功返回0;失敗返回錯誤碼
??【代碼示例】?
void *threadRun(void* args)
{const char*name = static_cast<const char *>(args);int cnt = 5;while(cnt){cout << name << " is running: " << cnt-- << " obtain self id: " << pthread_self() << endl;sleep(1);}pthread_exit((void*)11); // PTHREAD_CANCELED; #define PTHREAD_CANCELED ((void *) -1)
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadRun, (void*)"thread 1");sleep(3);pthread_cancel(tid);void *ret = nullptr;pthread_join(tid, &ret);cout << " new thread exit : " << (int64_t)ret << "quit thread: " << tid << endl;return 0;
}
【輸出結果】(三秒后取消操作)
其次就是在上述代碼中,使用了線程等待函數,那為什么需要線程等待? ?
- 已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內。
- 創建新的線程不會復用剛才退出線程的地址空間。
?【函數介紹】
功能:等待線程結束原型int pthread_join(pthread_t thread, void **value_ptr);參數
thread:線程ID
value_ptr:它指向一個指針,后者指向線程的返回值返回值:成功返回0;失敗返回錯誤碼
- 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參數。
(四)分離線程
- 默認情況下,新創建的線程是joinable的,線程退出后,需要對其進行pthread_join操作,否則無法釋放資源,從而造成系統泄漏。
- 如果不關心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統,當線程退出時,自動釋放線程資源。
?【函數介紹】
int pthread_detach(pthread_t thread);
【代碼示例】?
void* threadRun(void* args) { sleep(2); cout << "線程任務已完成\n"; return nullptr;
} int main() { pthread_t tid; int ret; // 創建線程 ret = pthread_create(&tid, nullptr, threadRun, nullptr); if (ret) { cerr << "Error: pthread_create() failed\n"; return 1; } // 分離線程 pthread_detach(tid); // 主線程繼續執行 cout << "主線程繼續執行...\n"; // 主線程休眠一段時間以便觀察分離線程的輸出 sleep(3); // 主線程結束,分離線程的資源會在其完成后由系統自動回收 return 0;
}