POSIX線程庫(用戶級)
與線程有關的函數構成了一個完整的系列,絕大多數函數的名字以“pthread_”開頭
要使用這些函數庫,要通過引入頭文
鏈接這些線程函數庫時要使用編譯器命令的“-lpthread”選項
用戶線程和內核線程是1對1的
一、線程創建
- pthread_create函數
頭文件:#include<pthread.h>
函數原型:int pthread_create(pthread_t * thread,const pthread_attr_t *attr,void *(*start_routine)(void*),void *arg);
函數功能:創建一個新的線程
返回值:成功返回0,失敗返回錯誤碼
參數:
thread:返回線程ID
attr:設置線程的屬性,attr爲NULL表示使用默認屬性
start_routine:是一個函數地址。線程啓動後要執行的函數
arg:傳給線程啓動函數的參數
注:pthread_t 無符號長整型
下面是創建線程代碼的具體實現:
#include <stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<pthread.h>
void *thread_run(void *arg)
{
while(1)
{
printf("new pthread,thread is : %u,pid :%d\n",pthread_self(),getpid());
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,NULL);
while(1)
{
printf("main pthread,thread is : %u,pid %d\n",pthread_self(),getpid());
sleep(3);
}
return 0;
}
按照正常寫Makefile的方法寫會出現下面的問題
所以要在Makefile中加上“-lpthread”
就會運行出正確的結果
沒有線程之前,一個進程對應內核裏的一個進程描述符,對應一個進程ID。但是引入線程概念之後,情況發生了變化,一個用戶進程下管轄N個用戶態線程,每個線程作爲一個獨立的調度實體在內核態都有自己的進程描述符,進程和內核的描述符(task_struct)一下子就變成了1:N的關係(一個進程內可以有多個線程)
二、線程等待
線程以阻塞方式進行等待
爲什麼需要線程等待?
1.已經退出的線程,其空間沒有被釋放,仍然在進程的地址空間內
2.創建新的線程不會複用剛纔退出線程的地址空間pthread_join函數
頭文件:#include<pthread.h>
函數原型:int pthread_join(pthread_t thread,void** value_ptr);
函數功能:讓主線程阻塞,直到所有線程都已經退出。
返回值:調用成功返回0,其他值都爲失敗
參數:
thread:等待退出線程的線程號
value_ptr:退出線程的返回值
- 此函數的作用:
1.保證退出時的退出順序
2.回收新線程退出時的資源情況
3.獲得新線程退出時結果是否正確的退出返回值
線程等待的具體代碼實現:
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
void *thread1(void *arg)
{
printf("thread1 returning...\n");
int *p = (int*)malloc(sizeof(int));
*p = 1;
return (void *)p;
}
void *thread2(void *arg)
{
printf("thread2 exiting...\n");
int *p = (int*)malloc(sizeof(int));
*p = 2;
pthread_exit((void*)p);
}
void *thread3(void *arg)
{
while(1)
{
printf("thread3 is running...\n");
sleep(1);
}
return NULL;
}
int main()
{
pthread_t tid;
void *ret;
//thread1
pthread_create(&tid,NULL,thread1,NULL);
pthread_join(tid,&ret);
printf("thread return,thread tid : %X, return code : %d\n",tid,*(int*)ret);
free(ret);
//thread2
pthread_create(&tid,NULL,thread2,NULL);
pthread_join(tid,&ret);
printf("thread return,thread tid : %X, return code : %d\n",tid,*(int*)ret);
free(ret);
//thread3
pthread_create(&tid,NULL,thread3,NULL);
sleep(3);
pthread_cancel(tid);
pthread_join(tid,&ret);
if(ret == PTHREAD_CANCELED)
{
printf("thread return,thread tid : %X, return code : PTHREAD_CANCELED\n",tid);
}
else
{
printf("thread return,thread tid : %X, return code : NULL\n",tid);
}
free(ret);
return 0;
}
運行結果如圖所示:
三、線程終止
A.進程負責線程正確執行
B.父進程負責進程正確執行
C.執行流負責函數正確執行,想讓父進程獲得子進程退出碼必須以進程退出方式
如果需要只終止某個線程而不是終止整個進程,可以有三種方法:
1.從線程函數return;這種方法對主線程不適用。從main函數return相當於調用exit
2.線程可以調用pthread_exit終止自己
3.一個線程可以調用pthread_cancel終止同一進程中的另一個線程進程退出的兩種方式:
1.調用exit();
2.主進程return;pthread_exit函數
頭文件:#include<pthread.h>
函數原型:void pthread_exit(void *value_ptr);
函數功能:線程終止
返回值:無返回值,和進程一樣,線程結束的時候無法返回到它的調用者(自身)
參數:
value_ptr:不要指向一個局部變量
注:pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因爲當其他線程得到這個返回指針時線程函數已經退出
線程終止的具體代碼實現:
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<pthread.h>
void *thread_run(void *arg)
{
sleep(5);
printf("new pthread,thread is : %u , pid : %d\n",pthread_self(),getpid());
pthread_exit((void*)123);
}
int main()
{
pthread_t tid;
pthread_create(&tid,NULL,thread_run,NULL);
void *ret;
pthread_join(tid,&ret);
printf("join new thread success,ret :%d\n",*(int *)ret);
return 0;
}
5秒後線程終止,主線程join成功
運行結果如圖所示
- pthread_cancel函數
頭文件:#include<pthread.h>
函數原型:int pthread_cancel(pthread_t thread);
函數功能:取消一個執行中的線程
返回值:成功返回0,失敗返回錯誤碼
參數:
thread:線程ID
四、線程的分離與結合屬性:
在任何一個時間點上,線程是可結合的(joinable),或者是分離的(detached)。joinable和分離是衝突的,一個線程不能既是joinable又是分離的。
可結合屬性:一個可結合的線程能夠被其他線程收回其資源和殺死;在被其他線程回收之前,它的存儲器資源(如棧)是不釋放的。
不可結合屬性:一個分離的線程是不能被其他線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。
線程不管怎麼分離都會在進程內部執行
默認情況下,線程被創建成可結合的。
1.爲了避免存儲器泄漏,每個可結合線程都應該被顯示的回收(調用pthread_join)
當在可結合狀態下,只有當pthread_join()函數返回時,創建的線程纔算終止,才能釋放自己佔用的系統資源,如果一個可結合線程結束運行但沒有被join,則它的狀態類似於殭屍進程還有一部分資源沒有被回收
2.或者通過設置可分離屬性
(1)線程調用函數:pthread_detach(pthread_self());
(2)主線程調用函數:pthread_detach(thread_id);
可分離線程沒有被其他的線程所等待,自己運行結束,線程就終止了,馬上釋放系統資源。一個進程即便被分離,只要發生異常,進程也會被影響
- 爲什麼要引入線程的可分離屬性呢??
主線程調用pthread_join後,如果該線程沒有運行結束,主線程會被阻塞,當主線程還要創建新線程來做一些事情時,此時的主線程因爲調用pthread_join而被阻塞,就沒有辦法處理其他事務。所以引入線程的可分離屬性,它不需要主線程來回收它,在其退出系統時自動被系統回收。