😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
⏰发布时间⏰:2024-03-22 09:05:41
本文未经允许,不得转发!!!
🎄一、概述
Linux线程库接口包括线程的创建、 退出、 取消和分离, 以及连接已经终止的线程, 互斥量, 读写锁,线程的条件等待等。
POSIX 函数 | 函数功能描述 |
---|---|
pthread_create | 创建一个线程 |
pthread_exit | 退出线程 |
pthread_self | 获取线程ID |
pthread_equal | 检查两个线程ID是否相等 |
pthread_join | 等待线程退出 |
pthread_detach | 设置线程状态为分离状态 |
pthread_cancel | 线程的取消 |
pthread_cleanup_push、pthread_cleanup_pop | 线程退出,清理函数注册和执行 |
在代码里使用到上述接口函数时,使用gcc编程过程中需要加-pthread
选项。
本文将介绍线程创建相关的一些知识,从pthread_create开始,然后依次介绍该函数第一个参数相关的线程ID,以及第二个函数相关的线程属性。
🎄二、线程的创建 pthread_create
程序开始启动的时候, 产生的进程只有一个线程, 我们称之为主线程或初始线程。 对于单线程的进程而言, 只存在主线程一个线程。 如果想在主线程之外, 再创建一个或多个线程, 就需要 pthread_create 函数。
pthread_create 函数原型:
#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.
- 函数描述:函数
pthread_create
在调用过程中启动一个新线程。新线程通过调用start_routine
参数指向的函数开始执行;arg
参数作为start_routine
的唯一参数传递。 - 函数参数:
-
thread
:传出参数,thread
参数是pthread_t
类型的指针,线程创建成功的话,会将分配的线程ID填入该指针指向的地址。线程的后续操作将使用该值作为线程的唯一标识。 -
attr
:传入参数,第二个参数是pthread_attr_t
类型,通过该参数可以定制线程的属性,比如可以指定新建线程栈的大小、调度策略等。 如果创建线程无特殊的要求, 该值也可以是NULL, 表示采用默认属性。 -
start_routine
:传入参数,第三个参数是线程需要执行的函数。创建线程,是为了让线程执行一定的任务。线程创建成功之后,该线程就会执行start_routine函数,该函数之于线程,就如同main
函数之于主线程。 -
arg
:第四个参数是新建线程执行的start_routine
函数的入参。新建线程如果想要正常工作,则可能需要入参,那么主线程在调用pthread_create
的时候,就可以将入参的指针放入第四个参数以传递给新建线程。如果多个入参,可以使用结构体指针。
-
- 函数返回值:如果成功,则
pthread_create
返回0;如果不成功,则pthread_create
返回一个非0的错误码。pthread_create函数有点不同, 它会将errno作为返回值, 而不是一个负值。- EAGAIN:系统资源不够,或者创建线程的个数超过系统对一个进程中线程总数的限制
- EINVAL:第二个参数attr值不合法
- EPERM:没有合适的权限来设置调度策略或参数
通过上面的描述可以看到,pthread_create 是一个"四针"函数,也就是说它四个参数都是指针。下面是这个函数的简单使用示例:
// 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 成功创建线程后,第一个参数会返回所创建线程的线程ID,这个线程ID不同于使用系统调用函数syscall(SYS_gettid)
获得的线程ID。syscall(SYS_gettid)
的ID是进程调度的范畴;而这里返回的线程ID是操作系统调度器用来标识线程的。
pthread_t
到底是个什么样的数据结构呢? 因为POSIX标准并没有限制pthread_t的数据类型, 所以该类型取决于具体实现。 对于Linux目前使用的NPTL实现而言, pthread_t类型的线程ID, 本质就是一个进程地址空间上的一个地址。typedef unsigned long int pthread_t;
pthread_t
类型在Linux系统中定义在<bits/pthreadtypes.h>
头文件中,在Ubuntu可以使用命令vi /usr/include/bits/pthreadtypes.h
来查看,其完整定义如上,是unsigned long int
类型的。
✨2.1 线程ID相关函数
关于线程ID,线程库NPTL提供了pthread_self
、pthread_equal
两个函数来操作线程ID,它们的函数原型如下:
#include <pthread.h>
pthread_t pthread_self(void);
int pthread_equal(pthread_t t1, pthread_t t2);
Compile and link with -pthread.
pthread_self
函数用于在线程指向函数中获取自身线程ID,这个函数不会调用失败,返回值就是线程ID;
pthread_equal
函数用于比较两个线程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复用
在满足下列条件时, 线程ID就有可能会被复用:
1) 线程退出。
2) 线程组的其他线程对该线程执行了pthread_join, 或者线程退出前将分离状态设置为已分离。
3) 再次调用pthread_create创建线程。
看例子:
// 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;
}
运行结果,可以看到两次的线程ID是一样的:
🎄四、线程属性
pthread_create 的第二个参数是线程属性,先看看 pthread_attr_t
结构体的定义:
typedef struct
{
int detachstate; //线程的分离状态
int schedpolicy; //线程调度策略
struct sched_param schedparam; //线程的调度参数
int inheritsched; //线程的继承性
int scope; //线程的作用域(竞争范围)
size_t guardsize; //线程栈末尾的警戒缓冲区大小
int stackaddr_set; //线程的栈设置
void * stackaddr; //线程栈的位置
size_t stacksize; //线程栈的大小
}pthread_attr_t;
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
功能:初始化/销毁线程的属性结构体
-
detachstate
:分离状态- PTHREAD_CREATE_JOINABLE(默认值):线程执行完函数后不会自行释放资源;
- PTHREAD_CANCEL_DEFERRED:线程执行完函数后,会自行终止并释放占用的资源。
系统提供两个函数获取、设置分离状态。另外,pthread_detach函数也可以设置线程分离。
int pthread_attr_getdetachstate(const pthread_attr_t * attr,int * detachstate); int pthread_attr_setdetachstate(pthread_attr_t *sttr,int detachstate);
-
schedpolicy
:调度策略- SCHED_OTHER(默认值):普通策略(分时调度算法),按照优先级调度
- SCHED_FIFO:先进先出。一个FIFO会持续执行,直到线程阻塞、结束、有更高优先级的线程就绪
- SCHED_RR:轮转策略。给每个线程分配执行时间(时间片),当一个线程的时间片耗尽时,下一个线程执行
其中,SCHED_OTHER 调度算法不支持为线程设置优先级,而另外两种调度算法支持。获取、设置的函数如下:
int pthread_attr_getschedpolicy(const pthread_attr_t *, int * policy) int pthread_attr_setschedpolicy(pthread_attr_*, int policy)
-
schedparam
:调度参数
用于设置线程的优先级(默认值为 0),该属性仅当线程的schedpolicy
属性为 SCHED_FIFO 或者 SCHED_RR 时才能发挥作用。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 调度属性(schedpolicy、schedparam)继承自创建者的
- PTHREAD_EXPLICIT_SCHED 使用attr创建的线程,从attr指定的值中获取其调度属性(schedpolicy、schedparam)。
获取、设置函数如下:
int pthread_attr_setinheritsched(pthread_attr_t *attr,int inheritsched); int pthread_attr_getinheritsched(pthread_attr_t *attr,int *inheritsched);
-
scope
:作用域(竞争范围)- PTHREAD_SCOPE_SYSTEM:在系统范围内竞争资源
- PTHREAD_SCOPE_PROCESS:在进程范围内竞争资源
线程执行过程中,可以只和同进程内的其它线程争夺 CPU 资源,也可以和系统中所有的其它线程争夺 CPU 资源,scope 属性用于指定目标线程和哪些线程抢夺 CPU 资源。获取、设置函数如下:
int pthread_attr_setscope(pthread_attr_t *attr, int scope); int pthread_attr_getscope(pthread_attr_t *attr, int *scope);
-
guardsize
:线程栈末尾的警戒缓冲区大小
每个线程中,栈内存的后面都紧挨着一块空闲的内存空间,我们通常称这块内存为警戒缓冲区,它的功能是:一旦我们使用的栈空间超出了额定值,警戒缓冲区可以确保线程不会因“栈溢出”立刻执行崩溃。int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize); int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize);
-
stackaddr_set
:线程的栈设置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() */
}
运行结果,打印一些默认值:
🎄五、总结
本文介绍了线程创建相关的内容,包括pthread_create函数的详细介绍和使用例子,然后依次介绍该函数第一个参数相关的线程ID知识以及第二个参数相关的线程属性知识。读完完整地了解线程的创建。
补充:
进程的地址空间:
1、Linux系统中,/proc/sys/vm/legacy_va_layout
文件的值会影响进程地址空间的布局。默认值是0,表示mmap区域的基地址在栈的下面, mmap区域从高地址向低地址扩展;若值为1, 那么mmap的基地址mmap_base变小(约在128T的三分之一处),mmap区域从低地址向高地址扩展。
2、使用命令 pmap PID
或 cat /proc/PID/maps
可以查看进程的地址空间:
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁文章来源:https://www.toymoban.com/news/detail-843519.html
参考资料:
https://blog.csdn.net/qq_41854911/article/details/118719001
《Linux环境编程:从应用到内核》文章来源地址https://www.toymoban.com/news/detail-843519.html
到了这里,关于【Linux C | 多线程编程】线程的创建、线程ID、线程属性的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!