APUE學習之多線程編程(一):線程的創建和銷毀,apue多線程編程
APUE學習之多線程編程(一):線程的創建和銷毀,apue多線程編程
一、線程標識
和每個進程都有一個進程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傳入。
線程新建後會繼承調用線程的浮點環境和屏蔽字。
例子:

![]()
#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。

![]()
./a.out
main thread: pid 27335 tid 3076404928 (0xb75e36c0)
new thread: pid 27335 tid 3076401984 (0xb75e2b40)
View Code
雖然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就會調用失敗。
例子:

![]()
#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;
}
View Code

![]()
./a.out
thread 2 exiting
thread 1 returning
thread1 exit code:1
thread2 exit code:2
View Code
也可傳遞包含復雜消息的結構的地址,不過必須注意,這個結構所使用的內存必須在完成調用後仍是有效的。
線程也可以調用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時。
例子:

![]()
#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

![]()
./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
View Code
可知如果線程是通過它的啟動例程中返回而終止的話,它的清理處理程序就不會被調用。
在默認情況下,線程的終止狀態會一直保存到對該線程調用pthread_join,如果該線程已經被分離,則底層的資源可以在線程終止時立即被收回,不用再調用pthread_join,且再調用pthread_join會出錯。
#include <pthread.h>
int pthread_detach(pthread_t tid);
http://xxxxxx/Linuxjc/1150222.html TechArticle