??博客主頁??:??https://blog.csdn.net/wkd_007??
??博客內(nèi)容??:??嵌入式開發(fā)、Linux、C語言、C++、數(shù)據(jù)結(jié)構(gòu)、音視頻??
?發(fā)布時間?:2024-03-22 09:05:41
本文未經(jīng)允許,不得轉(zhuǎn)發(fā)?。。?/font>
??一、概述
Linux線程庫接口包括線程的創(chuàng)建、 退出、 取消和分離, 以及連接已經(jīng)終止的線程, 互斥量, 讀寫鎖,線程的條件等待等。
POSIX 函數(shù) | 函數(shù)功能描述 |
---|---|
pthread_create | 創(chuàng)建一個線程 |
pthread_exit | 退出線程 |
pthread_self | 獲取線程ID |
pthread_equal | 檢查兩個線程ID是否相等 |
pthread_join | 等待線程退出 |
pthread_detach | 設(shè)置線程狀態(tài)為分離狀態(tài) |
pthread_cancel | 線程的取消 |
pthread_cleanup_push、pthread_cleanup_pop | 線程退出,清理函數(shù)注冊和執(zhí)行 |
在代碼里使用到上述接口函數(shù)時,使用gcc編程過程中需要加-pthread
選項。
本文將介紹線程創(chuàng)建相關(guān)的一些知識,從pthread_create開始,然后依次介紹該函數(shù)第一個參數(shù)相關(guān)的線程ID,以及第二個函數(shù)相關(guān)的線程屬性。
??二、線程的創(chuàng)建 pthread_create
程序開始啟動的時候, 產(chǎn)生的進程只有一個線程, 我們稱之為主線程或初始線程。 對于單線程的進程而言, 只存在主線程一個線程。 如果想在主線程之外, 再創(chuàng)建一個或多個線程, 就需要 pthread_create 函數(shù)。
pthread_create 函數(shù)原型:
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.
- 函數(shù)描述:函數(shù)
pthread_create
在調(diào)用過程中啟動一個新線程。新線程通過調(diào)用start_routine
參數(shù)指向的函數(shù)開始執(zhí)行;arg
參數(shù)作為start_routine
的唯一參數(shù)傳遞。 - 函數(shù)參數(shù):
-
thread
:傳出參數(shù),thread
參數(shù)是pthread_t
類型的指針,線程創(chuàng)建成功的話,會將分配的線程ID填入該指針指向的地址。線程的后續(xù)操作將使用該值作為線程的唯一標(biāo)識。 -
attr
:傳入?yún)?shù),第二個參數(shù)是pthread_attr_t
類型,通過該參數(shù)可以定制線程的屬性,比如可以指定新建線程棧的大小、調(diào)度策略等。 如果創(chuàng)建線程無特殊的要求, 該值也可以是NULL, 表示采用默認(rèn)屬性。 -
start_routine
:傳入?yún)?shù),第三個參數(shù)是線程需要執(zhí)行的函數(shù)。創(chuàng)建線程,是為了讓線程執(zhí)行一定的任務(wù)。線程創(chuàng)建成功之后,該線程就會執(zhí)行start_routine函數(shù),該函數(shù)之于線程,就如同main
函數(shù)之于主線程。 -
arg
:第四個參數(shù)是新建線程執(zhí)行的start_routine
函數(shù)的入?yún)?。新建線程如果想要正常工作,則可能需要入?yún)?,那么主線程在調(diào)用pthread_create
的時候,就可以將入?yún)⒌闹羔樂湃氲谒膫€參數(shù)以傳遞給新建線程。如果多個入?yún)?,可以使用結(jié)構(gòu)體指針。
-
- 函數(shù)返回值:如果成功,則
pthread_create
返回0;如果不成功,則pthread_create
返回一個非0的錯誤碼。pthread_create函數(shù)有點不同, 它會將errno作為返回值, 而不是一個負值。- EAGAIN:系統(tǒng)資源不夠,或者創(chuàng)建線程的個數(shù)超過系統(tǒng)對一個進程中線程總數(shù)的限制
- EINVAL:第二個參數(shù)attr值不合法
- EPERM:沒有合適的權(quán)限來設(shè)置調(diào)度策略或參數(shù)
通過上面的描述可以看到,pthread_create 是一個"四針"函數(shù),也就是說它四個參數(shù)都是指針。下面是這個函數(shù)的簡單使用示例:
// 02_pthread_create
// 編譯:gcc 02_pthread_create.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func(void *arg)
{
int *parg = arg;
printf("this thread arg is %d \n", *parg);
return NULL;
}
int main()
{
int arg=10;
pthread_t threadId;
pthread_create(&threadId, NULL, func, &arg);
while(1); // 讓主線程不退出
return 0;
}
??三、線程ID
通過 pthread_create 成功創(chuàng)建線程后,第一個參數(shù)會返回所創(chuàng)建線程的線程ID,這個線程ID不同于使用系統(tǒng)調(diào)用函數(shù)syscall(SYS_gettid)
獲得的線程ID。syscall(SYS_gettid)
的ID是進程調(diào)度的范疇;而這里返回的線程ID是操作系統(tǒng)調(diào)度器用來標(biāo)識線程的。
pthread_t
到底是個什么樣的數(shù)據(jù)結(jié)構(gòu)呢? 因為POSIX標(biāo)準(zhǔn)并沒有限制pthread_t的數(shù)據(jù)類型, 所以該類型取決于具體實現(xiàn)。 對于Linux目前使用的NPTL實現(xiàn)而言, pthread_t類型的線程ID, 本質(zhì)就是一個進程地址空間上的一個地址。typedef unsigned long int pthread_t;
pthread_t
類型在Linux系統(tǒng)中定義在<bits/pthreadtypes.h>
頭文件中,在Ubuntu可以使用命令vi /usr/include/bits/pthreadtypes.h
來查看,其完整定義如上,是unsigned long int
類型的。
?2.1 線程ID相關(guān)函數(shù)
關(guān)于線程ID,線程庫NPTL提供了pthread_self
、pthread_equal
兩個函數(shù)來操作線程ID,它們的函數(shù)原型如下:
#include <pthread.h>
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
Compile and link with -pthread.
pthread_self
函數(shù)用于在線程指向函數(shù)中獲取自身線程ID,這個函數(shù)不會調(diào)用失敗,返回值就是線程ID;
pthread_equal
函數(shù)用于比較兩個線程ID是否相等,返回值是0的時候, 表示兩個線程是同一個線程, 非零值則表示不是同一個線程。注意,比較線程ID只有在同一個進程中才有意義。
??舉例子:
// 03_pthreadID.c
// gcc 03_pthreadID.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func1(void *arg)
{
int *parg = arg;
printf("this thread arg is %d, my threadID is %lx \n", *parg, (unsigned long)pthread_self());
while(1); // 讓線程不退出
}
void *func2(void *arg)
{
pthread_t *parg = arg;
printf("other threadId is %lx, my threadID is %lx \n", (unsigned long)*parg, (unsigned long)pthread_self());
while(1); // 讓線程不退出
}
int main()
{
int arg=10;
pthread_t threadId_1;
pthread_create(&threadId_1, NULL, func1, &arg);
pthread_t threadId_2;
pthread_create(&threadId_2, NULL, func2, &threadId_1);
if(0 == pthread_equal(threadId_1,threadId_1))
printf("same threads\n");
else
printf("different threads\n");
while(1); // 讓主線程不退出
return 0;
}
?2.2 線程ID復(fù)用
在滿足下列條件時, 線程ID就有可能會被復(fù)用:
1) 線程退出。
2) 線程組的其他線程對該線程執(zhí)行了pthread_join, 或者線程退出前將分離狀態(tài)設(shè)置為已分離。
3) 再次調(diào)用pthread_create創(chuàng)建線程。
看例子:
// 04_pthreadID_reuse.c
// gcc 04_pthreadID_reuse.c -l pthread
#include <stdio.h>
#include <pthread.h>
void *func(void *arg)
{
int *parg = arg;
printf("this thread arg is %d, my threadID is %lx \n", *parg, (unsigned long)pthread_self());
return NULL;
}
int main()
{
int arg=10;
pthread_t threadId;
pthread_create(&threadId, NULL, func, &arg);
pthread_join(threadId,NULL); // 等待線程退出
pthread_create(&threadId, NULL, func, &arg);
while(1); // 讓主線程不退出
return 0;
}
運行結(jié)果,可以看到兩次的線程ID是一樣的:
??四、線程屬性
pthread_create 的第二個參數(shù)是線程屬性,先看看 pthread_attr_t
結(jié)構(gòu)體的定義:
typedef struct
{
int detachstate; //線程的分離狀態(tài)
int schedpolicy; //線程調(diào)度策略
struct sched_param schedparam; //線程的調(diào)度參數(shù)
int inheritsched; //線程的繼承性
int scope; //線程的作用域(競爭范圍)
size_t guardsize; //線程棧末尾的警戒緩沖區(qū)大小
int stackaddr_set; //線程的棧設(shè)置
void * stackaddr; //線程棧的位置
size_t stacksize; //線程棧的大小
}pthread_attr_t;
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
功能:初始化/銷毀線程的屬性結(jié)構(gòu)體
-
detachstate
:分離狀態(tài)- PTHREAD_CREATE_JOINABLE(默認(rèn)值):線程執(zhí)行完函數(shù)后不會自行釋放資源;
- PTHREAD_CANCEL_DEFERRED:線程執(zhí)行完函數(shù)后,會自行終止并釋放占用的資源。
系統(tǒng)提供兩個函數(shù)獲取、設(shè)置分離狀態(tài)。另外,pthread_detach函數(shù)也可以設(shè)置線程分離。
int pthread_attr_getdetachstate(const pthread_attr_t * attr,int * detachstate); int pthread_attr_setdetachstate(pthread_attr_t *sttr,int detachstate);
-
schedpolicy
:調(diào)度策略- SCHED_OTHER(默認(rèn)值):普通策略(分時調(diào)度算法),按照優(yōu)先級調(diào)度
- SCHED_FIFO:先進先出。一個FIFO會持續(xù)執(zhí)行,直到線程阻塞、結(jié)束、有更高優(yōu)先級的線程就緒
- SCHED_RR:輪轉(zhuǎn)策略。給每個線程分配執(zhí)行時間(時間片),當(dāng)一個線程的時間片耗盡時,下一個線程執(zhí)行
其中,SCHED_OTHER 調(diào)度算法不支持為線程設(shè)置優(yōu)先級,而另外兩種調(diào)度算法支持。獲取、設(shè)置的函數(shù)如下:
int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy) int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
-
schedparam
:調(diào)度參數(shù)
用于設(shè)置線程的優(yōu)先級(默認(rèn)值為 0),該屬性僅當(dāng)線程的schedpolicy
屬性為 SCHED_FIFO 或者 SCHED_RR 時才能發(fā)揮作用。int pthread_attr_getschedparam(const pthread_attr_t *attr, struct sched_param *param); int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param); struct sched_param param; // sched_param 只有一個字段 sched_priority param.sched_priority = 99;
-
inheritsched
:繼承性- PTHREAD_INHERIT_SCHED 調(diào)度屬性(schedpolicy、schedparam)繼承自創(chuàng)建者的
- PTHREAD_EXPLICIT_SCHED 使用attr創(chuàng)建的線程,從attr指定的值中獲取其調(diào)度屬性(schedpolicy、schedparam)。
獲取、設(shè)置函數(shù)如下:
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched); int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);
-
scope
:作用域(競爭范圍)- PTHREAD_SCOPE_SYSTEM:在系統(tǒng)范圍內(nèi)競爭資源
- PTHREAD_SCOPE_PROCESS:在進程范圍內(nèi)競爭資源
線程執(zhí)行過程中,可以只和同進程內(nèi)的其它線程爭奪 CPU 資源,也可以和系統(tǒng)中所有的其它線程爭奪 CPU 資源,scope 屬性用于指定目標(biāo)線程和哪些線程搶奪 CPU 資源。獲取、設(shè)置函數(shù)如下:
int pthread_attr_setscope(pthread_attr_t *attr, int scope); int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
-
guardsize
:線程棧末尾的警戒緩沖區(qū)大小
每個線程中,棧內(nèi)存的后面都緊挨著一塊空閑的內(nèi)存空間,我們通常稱這塊內(nèi)存為警戒緩沖區(qū),它的功能是:一旦我們使用的??臻g超出了額定值,警戒緩沖區(qū)可以確保線程不會因“棧溢出”立刻執(zhí)行崩潰。int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize); int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
-
stackaddr_set
:線程的棧設(shè)置int pthread_attr_setstack(pthread_attr_t *attr,void *stackaddr, size_t stacksize); int pthread_attr_getstack(pthread_attr_t *attr,void **stackaddr, size_t *stacksize);
-
stackaddr
:線程棧的位置int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr); int pthread_attr_getstackaddr(pthread_attr_t *attr, void **stackaddr);
-
stacksize
:線程棧的大小int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize); int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
??舉例子:
// 05_dispaly_attr.c 這是man手冊的一個展示線程屬性的例子,可以仔細研究以下
// gcc 05_dispaly_attr.c -l pthread
#define _GNU_SOURCE /* To get pthread_getattr_np() declaration */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#define handle_error_en(en, msg) \
do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)
static void
display_pthread_attr(pthread_attr_t *attr, char *prefix)
{
int s, i;
size_t v;
void *stkaddr;
struct sched_param sp;
s = pthread_attr_getdetachstate(attr, &i);
if (s != 0)
handle_error_en(s, "pthread_attr_getdetachstate");
printf("%sDetach state = %s\n", prefix,
(i == PTHREAD_CREATE_DETACHED) ? "PTHREAD_CREATE_DETACHED" :
(i == PTHREAD_CREATE_JOINABLE) ? "PTHREAD_CREATE_JOINABLE" :
"???");
s = pthread_attr_getscope(attr, &i);
if (s != 0)
handle_error_en(s, "pthread_attr_getscope");
printf("%sScope = %s\n", prefix,
(i == PTHREAD_SCOPE_SYSTEM) ? "PTHREAD_SCOPE_SYSTEM" :
(i == PTHREAD_SCOPE_PROCESS) ? "PTHREAD_SCOPE_PROCESS" :
"???");
s = pthread_attr_getinheritsched(attr, &i);
if (s != 0)
handle_error_en(s, "pthread_attr_getinheritsched");
printf("%sInherit scheduler = %s\n", prefix,
(i == PTHREAD_INHERIT_SCHED) ? "PTHREAD_INHERIT_SCHED" :
(i == PTHREAD_EXPLICIT_SCHED) ? "PTHREAD_EXPLICIT_SCHED" :
"???");
s = pthread_attr_getschedpolicy(attr, &i);
if (s != 0)
handle_error_en(s, "pthread_attr_getschedpolicy");
printf("%sScheduling policy = %s\n", prefix,
(i == SCHED_OTHER) ? "SCHED_OTHER" :
(i == SCHED_FIFO) ? "SCHED_FIFO" :
(i == SCHED_RR) ? "SCHED_RR" :
"???");
s = pthread_attr_getschedparam(attr, &sp);
if (s != 0)
handle_error_en(s, "pthread_attr_getschedparam");
printf("%sScheduling priority = %d\n", prefix, sp.sched_priority);
s = pthread_attr_getguardsize(attr, &v);
if (s != 0)
handle_error_en(s, "pthread_attr_getguardsize");
printf("%sGuard size = %ld bytes\n", prefix, v);
s = pthread_attr_getstack(attr, &stkaddr, &v);
if (s != 0)
handle_error_en(s, "pthread_attr_getstack");
printf("%sStack address = %p\n", prefix, stkaddr);
printf("%sStack size = 0x%lx bytes\n", prefix, v);
}
static void *
thread_start(void *arg)
{
int s;
pthread_attr_t gattr;
/* pthread_getattr_np() is a non-standard GNU extension that
retrieves the attributes of the thread specified in its
first argument */
s = pthread_getattr_np(pthread_self(), &gattr);
if (s != 0)
handle_error_en(s, "pthread_getattr_np");
printf("Thread attributes:\n");
display_pthread_attr(&gattr, "\t");
exit(EXIT_SUCCESS); /* Terminate all threads */
}
int main(int argc, char *argv[])
{
pthread_t thr;
pthread_attr_t attr;
pthread_attr_t *attrp; /* NULL or &attr */
int s;
attrp = NULL;
/* If a command-line argument was supplied, use it to set the
stack-size attribute and set a few other thread attributes,
and set attrp pointing to thread attributes object */
if (argc > 1) {
int stack_size;
void *sp;
attrp = &attr;
s = pthread_attr_init(&attr);
if (s != 0)
handle_error_en(s, "pthread_attr_init");
s = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (s != 0)
handle_error_en(s, "pthread_attr_setdetachstate");
s = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
if (s != 0)
handle_error_en(s, "pthread_attr_setinheritsched");
stack_size = strtoul(argv[1], NULL, 0);
s = posix_memalign(&sp, sysconf(_SC_PAGESIZE), stack_size);
if (s != 0)
handle_error_en(s, "posix_memalign");
printf("posix_memalign() allocated at %p\n", sp);
s = pthread_attr_setstack(&attr, sp, stack_size);
if (s != 0)
handle_error_en(s, "pthread_attr_setstack");
}
s = pthread_create(&thr, attrp, &thread_start, NULL);
if (s != 0)
handle_error_en(s, "pthread_create");
if (attrp != NULL) {
s = pthread_attr_destroy(attrp);
if (s != 0)
handle_error_en(s, "pthread_attr_destroy");
}
pause(); /* Terminates when other thread calls exit() */
}
運行結(jié)果,打印一些默認(rèn)值:
??五、總結(jié)
本文介紹了線程創(chuàng)建相關(guān)的內(nèi)容,包括pthread_create函數(shù)的詳細介紹和使用例子,然后依次介紹該函數(shù)第一個參數(shù)相關(guān)的線程ID知識以及第二個參數(shù)相關(guān)的線程屬性知識。讀完完整地了解線程的創(chuàng)建。
補充:
進程的地址空間:
1、Linux系統(tǒng)中,/proc/sys/vm/legacy_va_layout
文件的值會影響進程地址空間的布局。默認(rèn)值是0,表示mmap區(qū)域的基地址在棧的下面, mmap區(qū)域從高地址向低地址擴展;若值為1, 那么mmap的基地址mmap_base變?。s在128T的三分之一處),mmap區(qū)域從低地址向高地址擴展。
2、使用命令 pmap PID
或 cat /proc/PID/maps
可以查看進程的地址空間:
如果文章有幫助的話,點贊??、收藏?,支持一波,謝謝 ??????文章來源:http://www.zghlxwxcb.cn/news/detail-843519.html
參考資料:
https://blog.csdn.net/qq_41854911/article/details/118719001
《Linux環(huán)境編程:從應(yīng)用到內(nèi)核》文章來源地址http://www.zghlxwxcb.cn/news/detail-843519.html
到了這里,關(guān)于【Linux C | 多線程編程】線程的創(chuàng)建、線程ID、線程屬性的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!