欢迎光临
我们一直在努力

【线程控制】一篇文章带你了解线程控制函数的使用

文章目录

  • 前言
  • 进程VS线程——哪些资源共享、哪些资源独占
  • 线程的控制
    • POSIX线程库
    • 创建线程:pthread_create
    • 等待线程:pthread_join
    • 终止线程:pthread_exit
    • 获取线程自己的id:pthread_self
    • 取消线程:pthread_cancel
  • 线程退出对应的返回值问题

前言

本篇文章我们来学习线程控制函数的使用方法,我将从线程函数的原型、参数解析、使用方式来带领着大家一起学习线程函数的使用,同时重点指出线程返回值的一些问题。

创作不易,如果觉得本篇文章对您有所帮助的话希望留下点赞、关注加收藏,您的支持就是我创作的最大动力

进程VS线程——哪些资源共享、哪些资源独占

在进行线程的操作之前,我想先和大家理清楚对于进程和线程,哪些资源是它们两个共享的,哪些资源是独占的,这样的话在我们操作线程的过程当中也会更加的清晰

  • 进程间具有独立性
  • 线程共享地址空间,也就是共享进程资源
  • 进程是资源分配的基本单位,线程是CPU调度的基本单位
  • 线程共享进程的数据,也拥有自己的部分“私有”数据,如:线程ID,线程的上下文数据、栈、errno、信号屏蔽字、调度优先级等
  • 单进程的多个线程共享代码段和数据段、文件描述符表、每种信号处理方式、当前工作目录、用户id和组id等

线程的控制

POSIX线程库

对线程库的要求形成了一个POSIX标准,标准要求如下:

  • 与线程相关的函数构成了一个完整的系列,绝大多数函数的名字都是以"pthread_"打头的
  • 要使用这些函数库,要通过引入头文件<pthread.h>
  • 链接这些线程库时要使用编译器命令的"-lpthread"选项,用来指定要连接的库

创建线程:pthread_create

函数原型如下所示: 在这里插入图片描述

功能:创建一个新的线程 参数解析: *restrict thread:输出型参数,创建一个线程成功后返回线程的id,那么这个id是不是LWP呢,答案是否定的,原因是这个id并不需要暴露给用户 pthread_attr_t *restrict attr:线程的属性,一般不需要设置 void *(start_routine)(void ):回调函数,返回值为void,参数是void void *restrict arg:传递给回调函数的参数 头文件:#include<pthread.h> 使用说明:要指定连接-pthread库 返回值:成功返回0,失败返回错误码

我们用下面的代码来演示这个函数的使用:

#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>

void*thread_routine(void*arg)
{
std::string name=static_cast<const char*>(arg);
while(true)
{
std::cout<<"子线程正在运行…"<<" 子线程id是:"<<name<<std::endl;
sleep(1);
}
}

int main()
{
pthread_t pid;
pthread_create(&pid,nullptr,thread_routine,(void*)"thread-1");
while(true)
{
std::cout<<"主线程正在运行…"<<std::endl;
sleep(1);
}
return 0;
}

运行结果如下: 在这里插入图片描述

等待线程:pthread_join

函数原型如下所示: 在这里插入图片描述

功能:阻塞等待,保证主线程最后一个退出,自动解决新线程的内存泄漏及僵尸问题 参数解析: pthread_t thread:输入型参数,想要等待的线程的id void **retval:输出型参数,获取新线程的退出信息,如果不关心就设置为nullptr 返回值:如果成功返回0,如果失败就返回出错时的错误码

我们用下面的代码来演示这个函数的使用:

#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>

void*thread_routine(void*arg)
{
std::string name=static_cast<const char*>(arg);
int cnt=5;
while(cnt)
{
std::cout<<"子线程正在运行…"<<" 子线程id是:"<<name<<std::endl;
sleep(1);
}
return nullptr;
}

int main()
{
pthread_t pid;
pthread_create(&pid,nullptr,thread_routine,(void*)"thread-1");

printf("创建了一个新线程,它的tid是:0X%lx\\n",pid);

pthread_join(pid,nullptr);
std::cout<<"主线程退出…"<<std::endl;
return 0;
}

运行结果如下所示: 在这里插入图片描述

终止线程:pthread_exit

函数原型如下所示: 在这里插入图片描述

功能:终止线程 参数解析: void *retval:线程退出时的退出码

我们用下面的代码来演示这个函数的使用:

#include<iostream>
#include<pthread.h>
#include<string>
#include<unistd.h>

void*thread_routine(void*arg)
{
std::string name=static_cast<const char*>(arg);
int cnt=5;
while(cnt)
{
std::cout<<"子线程正在运行…"<<" 子线程id是:"<<name<<std::endl;
sleep(1);
}
pthread_exit(nullptr);
}

int main()
{
pthread_t pid;
pthread_create(&pid,nullptr,thread_routine,(void*)"thread-1");

printf("创建了一个新线程,它的tid是:0X%lx\\n",pid);

pthread_join(pid,nullptr);
sleep(8);
std::cout<<"主线程退出…"<<std::endl;
return 0;
}

运行结果如下所示: 在这里插入图片描述

获取线程自己的id:pthread_self

函数原型如下所示: 在这里插入图片描述 这个函数我们不做过多的演示,当我们需要获取当前线程的id时就可以直接调用这个函数

取消线程:pthread_cancel

函数原型如下所示: 在这里插入图片描述 函数功能:让一个线程去取消另一个线程 返回值:成功返回0,失败返回对应的错误码

线程退出对应的返回值问题

这里的返回值问题我们重点来讨论pthread_join第二个参数的返回值问题

前面我们提到,这个函数的第二个参数实际上是一个输出型的参数,我们可以通过这个参数得到线程的返回值信息,未来我们在实际工作当中可以定义一批错误码,来记录返回值的信息,然后通过pthread_join这个函数来拿到这个返回值,从而去判断子线程的执行情况正确与否

但是此时我们遇到了这样的问题,获取新线程的执行结果,需要考虑异常问题吗?答案是不用考虑异常问题,因为新线程只要异常了,那么整个进程就会退出,主线程没有机会去关注到新线程的异常信息,所以join不提供异常的处理,那么如果线程出现了异常,就会由父进程回收,通过wait接口关注异常

那么此时就会出现这样的问题,新线程是如何把自己的返回值交给父进程的?父进程如何通过pthread_join获得指定子进程的退出信息?

当程序启动的时候,首先要加载虚拟地址空间,构建页表映射、加载内核代码和数据结构等等,我们所使用的库也需要加载到内存,然后通过页表映射到虚拟地址的共享区里面,那么一个进程创建的线程是谁提供的呢?实际上是pthread库为我们提供的,Linux操作系统只有轻量级进程,而我们创建的线程需要被管理起来,因此需要先描述再组织,所以pthread库必定为我们提供了一个描述线程的结构体 ,这个结构体的名称叫作TCB,这个结构体里面存在一个LWP轻量级进程id,而我们前面所打印的线程id实际上是一个虚拟地址,这个地址就是TCB结构体变量在库当中的起始地址,所以线程的返回值实际上是被写进了对应的TCB结构体里面,所以这也就解释了为什么pthread_join的第二个参数是一个二级指针,是因为我们需要先访问到这个TCB结构体,然后在这个TCB结构体里面拿到线程的返回值,所以第二个参数必须是一个二级指针

综上所述:我们把Linux下的线程叫作用户级线程

赞(0)
未经允许不得转载:171主机测评 » 【线程控制】一篇文章带你了解线程控制函数的使用
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址