一、線程辨別
和每個程序都有一個程序ID一樣,每個線程也有一個線程ID,線程ID是以pthread_t資料類型來表示的,在Linux中,用無符号長整型表示pthread_t,Solaris 把phread_t資料類型表示為無符号整型,FreeBSD 和Mac OS X 用一個指向pthread結構的指針來表示pthread_t資料類型。
可以使用pthread_self函數獲得自身的線程ID。
#include <pthread.h>
pthread_t pthread_self(void);
二、線程建立
使用pthread_create函數建立新線程
#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_arrt_t *restrict addr, void *(*start_rtn)(void *), void *restrict arg);
當pthread_create成功傳回後,新建立線程的線程ID會被設定成tidp指向的記憶體單元,attr參數用于定制各種不同的線程屬性,後面再讨論線程屬性,現在先把它置為null,建立一個具有預設屬性的線程。
新建立的線程從start_rtn函數開始運作,該函數接收一個無類型指針的參數arg,如果要傳給它的參數多于一個,可以把參數放到一個結構中,然後把結構的位址作為arg傳入。
線程建立後會繼承調用線程的浮點環境和屏蔽字。
例子:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
#include "apue.h"
#include <pthread.h>
pthread_t ntid;
void printids(const char *s)
{
pid_t pid;
pthread_t tid;
pid = getpid();
tid = pthread_self();
printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);
}
void *thr_fn(void *arg)
{
printids("new thread: ");
return ((void *)0);
}
int main(void)
{
int err;
err = pthread_create(&ntid, NULL, thr_fn, NULL);
if (err != 0)
{
err_exit(err, "can't create thread");
}
printids("main thread: ");
sleep(1);
exit(0);
}
View Code
這個程式有兩個特别的地方:第一,主線程需要休眠,如果主線程不休眠,主線程會退出,新線程并沒有機會運作。第二,新線程通過pthread_self(),獲得自己的線程ID。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
./a.out
main thread: pid 27335 tid 3076404928 (0xb75e36c0)
new thread: pid 27335 tid 3076401984 (0xb75e2b40)
雖然Linux線程ID是用無符号長整型來表示的,但它們看起來更像指針。
三、線程終止
如果任意線程調用了exit,_exit,_Exit,整個程序都會終止,這個要注意。
單個線程可以通過以下三種方式退出,且不終止整個程序。
1.線程可以簡單地從啟動例程中傳回,傳回值是線程的退出碼。
2.線程可以被同一程序中的其他線程取消。
3.調用pthread_exit
先來看pthread_exit退出的情況。
#include <pthread.h>
void pthread_exit(void *rval_ptr);
ravl_ptr是無類型指針,程序中的其他線程可以通過pthread_join函數獲得這個指針。
#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);
調用線程将一直阻塞,直至指定的線程退出,rval_ptr就包含傳回碼,如果線程被取消,rval_ptr指定的記憶體單元就設定為PTHREAD_CANCELED.可以通過調用pthread_join自動把線程置于分離狀态,如果線程已處于分離狀态,pthread_join就會調用失敗。
例子:
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
#include "apue.h"
#include <pthread.h>
void *thr_fn1(void *arg)
{
printf("thread 1 returning\n");
return (void *)1;
}
void *thr_fn2(void *arg)
{
printf("thread 2 exiting\n");
pthread_exit((void *)2);
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, NULL);
if (err != 0)
{
err_exit(err, "can't create thread1");
}
err = pthread_create(&tid2, NULL, thr_fn2, NULL);
if (err != 0)
{
err_exit(err, "can't create thread2");
}
err = pthread_join(tid1, &tret);
if (err != 0)
{
err_exit(err, "can't join thread1");
}
printf("thread1 exit code:%ld\n", (long)tret);
err = pthread_join(tid2, &tret);
if (err != 0)
{
err_exit(err, "can't join thread2");
}
printf("thread2 exit code:%ld\n", (long)tret);
return 0;
}
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
./a.out
thread 2 exiting
thread 1 returning
thread1 exit code:1
thread2 exit code:2
也可傳遞包含複雜消息的結構的位址,不過必須注意,這個結構所使用的記憶體必須在完成調用後仍是有效的。
線程也可以調用pthread_cancel函數來請求取消同一程序的其他線程
#include <pthread.h>
int pthread_cancel(pthread_t tid);
聽着有點霸道,不過也隻是請求而已,線程可以選擇忽略這個請求。
線程可以安排它退出時需要調用的函數,這樣的函數是由pthread_cleanup_push注冊在棧中的,是以執行順序與注冊時相反。
#include <pthread.h>
void pthread_cleanup_push(void(*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
當線程執行以下動作時,清理函數rtn由pthread_cleanup_push函數排程
1.調用pthread_exit時
2.響應取消請求時
3.用非零execute參數調用pthread_cleanup_pop時。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
#include "apue.h"
#include <pthread.h>
void cleanup(void *arg)
{
printf("cleanup: %s\n", (char *)arg);
}
void *thr_fn1(void *arg)
{
printf("thread 1 start\n");
pthread_cleanup_push(cleanup, "thread 1 first handler");
pthread_cleanup_push(cleanup, "thread 1 second handler");
printf("thread 1 push complete\n");
if (arg)
{
return (void *)1;
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return (void *)1;
}
void *thr_fn2(void *arg)
{
printf("thread 2 start\n");
pthread_cleanup_push(cleanup, "thread 2 first handler");
pthread_cleanup_push(cleanup, "thread 2 second handler");
printf("thread 2 push complete\n");
if (arg)
{
pthread_exit((void *)2);
}
pthread_cleanup_pop(0);
pthread_cleanup_pop(0);
return (void *)2;
}
int main(void)
{
int err;
pthread_t tid1, tid2;
void *tret;
err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
if (err != 0)
{
err_exit(err, "can't create thread 1");
}
err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
if (err != 0)
{
err_exit(err, "can't create thread 2");
}
err = pthread_join(tid1, &tret);
if (err != 0)
{
err_exit(err, "can't join with thread 1");
}
printf("thread 1 exit code %ld\n", (long)tret);
err = pthread_join(tid2, &tret);
if (err != 0)
{
err_exit(err, "can't join with thread 2");
}
printf("thread 2 exit code %ld\n", (long)tret);
return 0;
}
View Code
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIml2ZuUWYxYmZhR2MiNjY3QGMjFGZlNTOzUWZjJGO2YzM1MWMfdWbp9CXt92Yu4GZjlGbh5SZslmZxl3Lc9CX6MHc0RHaiojIsJye.gif)
./a.out
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 exit code 2
可知如果線程是通過它的啟動例程中傳回而終止的話,它的清理處理程式就不會被調用。
在預設情況下,線程的終止狀态會一直儲存到對該線程調用pthread_join,如果該線程已經被分離,則底層的資源可以線上程終止時立即被收回,不用再調用pthread_join,且再調用pthread_join會出錯。
#include <pthread.h>
int pthread_detach(pthread_t tid);