重慶模板建站軟件網(wǎng)站收錄服務(wù)
目錄
一.POSIX線程庫(kù)
二.線程創(chuàng)建
1.創(chuàng)建線程接口
2.查看線程
3.多線程的健壯性問(wèn)題
4.線程函數(shù)參數(shù)傳遞
5.線程id和地址空間
三.線程終止
1.pthread_exit
2.pthread_cancel
四.線程等待?
五.線程分離
一.POSIX線程庫(kù)
站在內(nèi)核的角度,OS只有輕量級(jí)進(jìn)程,沒(méi)有線程的概念,但是站在用戶的角度我們只有線程沒(méi)有輕量級(jí)進(jìn)程的概念。因?yàn)長(zhǎng)inux下沒(méi)有真正意義上的線程,而是用進(jìn)程模擬的線程,所以Linux不會(huì)提供直接創(chuàng)建線程的系統(tǒng)調(diào)用,最多給我們提供創(chuàng)建輕量級(jí)進(jìn)程的接口。
所以linux對(duì)下對(duì)LWP的接口進(jìn)行封裝,對(duì)上給用戶提供線程控制的接口——POSIX線程庫(kù),pthread庫(kù),這是任何一個(gè)linux系統(tǒng)都會(huì)帶的庫(kù),又叫原生線程庫(kù)。
POSIX線程:
- 與線程有關(guān)的函數(shù)構(gòu)成了一個(gè)完整的系列,絕大多數(shù)函數(shù)的名字都是以“pthread_”打頭的。
- 要使用這些函數(shù)庫(kù),要通過(guò)引入頭文<pthread.h>。
- 鏈接這些線程函數(shù)庫(kù)時(shí)要使用編譯器命令的“-lpthread”選項(xiàng)。
?二.線程創(chuàng)建
1.創(chuàng)建線程接口
功能:創(chuàng)建一個(gè)新的線程。
原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
參數(shù):
- thread:返回線程ID。
- attr:設(shè)置線程的屬性,attr為NULL表示使用默認(rèn)屬性。
- start_routine:是個(gè)函數(shù)地址,線程啟動(dòng)后要執(zhí)行的函數(shù),該函數(shù)返回值是void*,參數(shù)是void*。
- arg:傳給線程啟動(dòng)函數(shù)的參數(shù)。
返回值:
- 成功返回0;失敗返回錯(cuò)誤碼。
?測(cè)試代碼:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>
using namespace std;void *FuncRun(void *argc)
{while (1){cout << "I am thread,my pid:" << getpid() << endl;sleep(1);}
}int main()
{//線程idpthread_t id;//創(chuàng)建線程pthread_create(&id, NULL, FuncRun, NULL);while (1){cout << "I am main,my pid:" << getpid() << endl;sleep(1);}return 0;
}
測(cè)試結(jié)果:
說(shuō)明:
- 線程沒(méi)有父子之分,但是線程有主線程,和新線程的區(qū)分。
- 我們可以看到,主線程pid和新線程的pid是相同的。因?yàn)樗麄儽旧砭褪峭粋€(gè)進(jìn)程的一部分。
- 新線程和主線程誰(shuí)先被調(diào)度取決于調(diào)度器。
?2.查看線程
查看線程使用命令:
ps -aL
?3.多線程的健壯性問(wèn)題
?測(cè)試代碼:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;void *FuncRun1(void *argc)
{int count = 0;while (1){count++;cout << "I am thread1,my pid:" << getpid() << endl;if (count == 5){int tmp = count / 0;}sleep(1);}
}
void *FuncRun2(void *argc)
{while (1){cout << "I am thread2,my pid:" << getpid() << endl;sleep(1);}
}int main()
{// 線程idpthread_t id1, id2;// 創(chuàng)建線程pthread_create(&id1, NULL, FuncRun1, NULL);pthread_create(&id2, NULL, FuncRun2, NULL);while (1){cout << "I am main,my pid:" << getpid() << endl;sleep(1);}return 0;
}
測(cè)試結(jié)果:
說(shuō)明:
- 代碼其中有一個(gè)線程在第五秒的時(shí)候,會(huì)出現(xiàn)一個(gè)除0的問(wèn)題。
- 當(dāng)一個(gè)線程因?yàn)槟骋粋€(gè)錯(cuò)處而導(dǎo)致線程終止的時(shí)候,整個(gè)進(jìn)程也都會(huì)直接終止。
- 因?yàn)樾盘?hào)是發(fā)送給進(jìn)程的,最終OS直接對(duì)由信號(hào)做出的處理動(dòng)作也是針對(duì)進(jìn)程的。
?4.線程函數(shù)參數(shù)傳遞
?我們想通過(guò)給線程函數(shù)傳參讓線程執(zhí)行更加復(fù)雜的任務(wù)。
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;// 任務(wù),計(jì)算[1-top]的求和,并將結(jié)果存儲(chǔ)到sum中。
struct task
{task(int top, int num): _thread_name("thread" + to_string(num)), _top(top), _sum(0), _num(num){}string _thread_name; // 線程名字int _top; // 計(jì)算數(shù)據(jù)范圍int _sum; // 結(jié)果int _num; // 線程編號(hào)
};// 線程函數(shù)
void *FuncRun(void *argc)
{task *t = (task *)argc;for (int i = 1; i <= t->_top; i++){t->_sum += i;}
}int main()
{// 線程idpthread_t id1, id2;// 創(chuàng)建線程task t1(100, 1);task t2(150, 2);pthread_create(&id1, NULL, FuncRun, &t1);pthread_create(&id2, NULL, FuncRun, &t2);// 等待線程計(jì)算完再輸出結(jié)果sleep(1);cout << t1._thread_name << ":[1-" << t1._top << "]=" << t1._sum << endl;cout << t2._thread_name << ":[1-" << t2._top << "]=" << t2._sum << endl;return 0;
}
測(cè)試結(jié)果:
說(shuō)明:
- 由于接口的設(shè)計(jì)上參數(shù)的類型是void* ,這也就使得我們可以給線程函數(shù)傳遞的參數(shù)是非常豐富的。
5.線程id和地址空間
- pthread_ create 函數(shù)會(huì)產(chǎn)生一個(gè)線程ID,存放在第一個(gè)參數(shù)指向的地址中。該線程ID和前面說(shuō)的線程ID不是一回事。
- 前面講的線程ID屬于進(jìn)程調(diào)度的范疇。因?yàn)榫€程是輕量級(jí)進(jìn)程,是操作系統(tǒng)調(diào)度器的最小單位,所以需要一個(gè)數(shù)值來(lái)唯一表示該線程。
- pthread_ create 函數(shù)第一個(gè)參數(shù)指向一個(gè)虛擬內(nèi)存單元,該內(nèi)存單元的地址即為新創(chuàng)建線程的線程ID,屬于NPTL線程庫(kù)的范疇。線程庫(kù)的后續(xù)操作,就是根據(jù)該線程ID來(lái)操作線程的。線程庫(kù)NPTL提供了pthread_ self函數(shù),可以獲得線程自身的ID:
pthread_t pthread_self(void);
pthread_t 到底是什么類型呢?取決于實(shí)現(xiàn)。對(duì)于Linux目前實(shí)現(xiàn)的NPTL實(shí)現(xiàn)而言,pthread_t類型的線程ID,本質(zhì)就是一個(gè)進(jìn)程地址空間上的一個(gè)地址。
我們?cè)趯?duì)線程做操作的時(shí)候,根本上是使用線程庫(kù)對(duì)線程進(jìn)行操作,那么線程庫(kù)本質(zhì)就是存在于linux上的動(dòng)態(tài)庫(kù),在我們使用線程庫(kù)的時(shí)候,線程庫(kù)也會(huì)向普通的動(dòng)態(tài)庫(kù)一樣加載到共享區(qū)中,我們使用線程庫(kù)方法就是訪問(wèn)自己的地址空間。
線程庫(kù)中需要被管理的線程會(huì)有很多,線程庫(kù)也必然實(shí)現(xiàn)了線程的數(shù)據(jù)結(jié)構(gòu)——TCB(線程控制塊)。
每個(gè)線程都有自己的TCB,和獨(dú)立的上下文數(shù)據(jù),以及線程棧空間。pthread_t 就是指向他們的首地址的一個(gè)地址。
?三.線程終止
如果需要只終止某個(gè)線程而不終止整個(gè)進(jìn)程,可以有三種方法:
- 從線程函數(shù)return。這種方法對(duì)主線程不適用,從main函數(shù)return相當(dāng)于調(diào)用exit。
- 線程可以調(diào)用pthread_ exit終止自己。
- 一個(gè)線程可以調(diào)用pthread_ cancel終止同一進(jìn)程中的另一個(gè)線程。
1.pthread_exit
功能:線程終止.
原型:void pthread_exit(void *value_ptr);
參數(shù):value_ptr:value_ptr不要指向一個(gè)局部變量,返回線程結(jié)果。
返回值:無(wú)返回值,跟進(jìn)程一樣,線程結(jié)束的時(shí)候無(wú)法返回到它的調(diào)用者(自身)。
?測(cè)試代碼:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;void *FuncRun1(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-1-count:" << count << endl;sleep(1);// 三秒后線程1退出if (count == 3){pthread_exit(NULL);}}
}void *FuncRun2(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-2-count:" << count << endl;sleep(1);// 三秒后線程2退出if (count == 3){pthread_exit(NULL);}}
}int main()
{// 線程idpthread_t id1, id2;// 創(chuàng)建線程pthread_create(&id1, NULL, FuncRun1, NULL);pthread_create(&id2, NULL, FuncRun2, NULL);while (1){cout << "I am main,my pid:" << getpid() << endl;sleep(1);}return 0;
}
測(cè)試結(jié)果:
說(shuō)明:
- 線程在退出后,主線程并沒(méi)有受到影響,進(jìn)程也有沒(méi)受到影響。
- 需要注意,pthread_exit或者return返回的指針?biāo)赶虻膬?nèi)存單元必須是全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因?yàn)楫?dāng)其它線程得到這個(gè)返回指針時(shí)線程函數(shù)已經(jīng)退出了。
2.pthread_cancel
功能:取消一個(gè)執(zhí)行中的線程
原型:int pthread_cancel(pthread_t thread);
參數(shù):thread:線程ID
返回值:成功返回0;失敗返回錯(cuò)誤碼
?測(cè)試代碼:
#include <iostream>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using namespace std;void *FuncRun1(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-1-count:" << count << endl;sleep(1);}
}
void *FuncRun2(void *argc)
{int count = 0;while (1){count++;cout << "I am thread-2-count:" << count << endl;sleep(1);}
}
int main()
{// 線程idpthread_t id1, id2;// 創(chuàng)建線程pthread_create(&id1, NULL, FuncRun1, NULL);pthread_create(&id2, NULL, FuncRun2, NULL);int count = 0;while (1){count++;sleep(1);if (count == 3){// 三秒后終止線程pthread_cancel(id1);pthread_cancel(id2);}cout << "I am main" << endl;}return 0;
}
測(cè)試結(jié)果:
四.線程等待?
為什么需要線程等待?
- 已經(jīng)退出的線程,其空間沒(méi)有被釋放,仍然在進(jìn)程的地址空間內(nèi)。
- 創(chuàng)建新的線程不會(huì)復(fù)用剛才退出線程的地址空間。
?功能:等待線程結(jié)束。
原型:int pthread_join(pthread_t thread, void **value_ptr);
參數(shù):thread:線程ID。
value_ptr:它指向一個(gè)指針,后者指向線程的返回值。
返回值:成功返回0;失敗返回錯(cuò)誤碼。
調(diào)用該函數(shù)的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過(guò)pthread_join得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下:?
- 如果thread線程通過(guò)return返回,value_ ptr所指向的單元里存放的是thread線程函數(shù)的返回值。
- 如果thread線程被別的線程調(diào)用pthread_ cancel異常終掉,value_ ptr所指向的單元里存放的是常數(shù),PTHREAD_ CANCELED。
- 如果thread線程是自己調(diào)用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數(shù)。
- 如果對(duì)thread線程的終止?fàn)顟B(tài)不感興趣,可以傳NULL給value_ ptr參數(shù)。
?測(cè)試代碼:
void *FuncRun1(void *argc)
{int *top = (int *)argc;int *sum = new int;for (int i = 1; i <= *top; i++){*sum += i;}// 線程退出pthread_exit(sum);
}void *FuncRun2(void *argc)
{int *top = (int *)argc;int *sum = new int;for (int i = 1; i <= *top; i++){*sum += i;}// 線程退出return sum;
}void *FuncRun3(void *argc)
{int *top = (int *)argc;int *sum = new int;for (int i = 1; i <= *top; i++){*sum += i;sleep(1);}free(sum);// 線程退出
}int main()
{int top1 = 100;int top2 = 150;int top3 = 200;pthread_t id1;pthread_t id2;pthread_t id3;pthread_create(&id1, NULL, FuncRun1, &top1);pthread_create(&id2, NULL, FuncRun2, &top2);pthread_create(&id3, NULL, FuncRun3, &top3);pthread_cancel(id3);// 接受線程返回?cái)?shù)據(jù)void *ret_ptr1;void *ret_ptr2;void *ret_ptr3;// 等待線程pthread_join(id1, &ret_ptr1);pthread_join(id2, &ret_ptr2);pthread_join(id3, &ret_ptr3);cout << "ret1:" << *((int *)ret_ptr1) << endl;free(ret_ptr1);cout << "ret2:" << *((int *)ret_ptr2) << endl;free(ret_ptr2);if (ret_ptr3 == PTHREAD_CANCELED)cout << "ret3:PTHREAD_CANCELED" << endl;return 0;
}
測(cè)試結(jié)果:
五.線程分離
- ?默認(rèn)情況下,新創(chuàng)建的線程是joinable的,線程退出后,需要對(duì)其進(jìn)行pthread_join操作,否則無(wú)法釋放資源,從而造成系統(tǒng)泄漏。
- 如果不關(guān)心線程的返回值,join是一種負(fù)擔(dān),這個(gè)時(shí)候,我們可以告訴系統(tǒng),當(dāng)線程退出時(shí),自動(dòng)釋放線程資源。
int pthread_detach(pthread_t thread);
可以是線程組內(nèi)其他線程對(duì)目標(biāo)線程進(jìn)行分離,也可以是線程自己分離:
pthread_detach(pthread_self());
joinable和分離是沖突的,一個(gè)線程不能既是joinable又是分離的。
測(cè)試代碼:
#include <iostream>
#include <cstdio>
#include <cstring>
#include <cerrno>
#include <pthread.h>
#include <unistd.h>using namespace std;
void *FuncRun(void *argc)
{// 線程分離pthread_detach(pthread_self());int count = 0;while (1){count++;cout << "I am thread-count:" << count << endl;sleep(1);}
}int main()
{pthread_t tid;int n = pthread_create(&tid, NULL, FuncRun, NULL);if (n != 0){cerr << "pthread_create:" << strerror(errno) << endl;}sleep(2);// 線程已經(jīng)分離,再去線程等待,pthread_join會(huì)立即報(bào)錯(cuò)。if (pthread_join(tid, NULL) == 0){printf("pthread wait success\n");}else{printf("pthread wait failed\n");}return 0;
}
測(cè)試結(jié)果:
?