Linux:线程控制和原生线程库

news2024/11/20 11:20:22

文章目录

  • 线程的id和LWP
  • 线程的终止
  • 线程的返回值问题
  • 关于原生线程库问题

本篇总结的内容主要是关于线程的控制专题

线程的id和LWP

对于获取线程的id来说,在Linux系统中存在这样的调用

在这里插入图片描述

这个调用就可以获取返回当前线程的id

先写出下面的实例代码

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

// 新线程
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while (true)
    {
        cout << threadname << " thread: "
             << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
        sleep(1);
    }
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread-1");
    while (true)
    {
        cout << "main thread: "
             << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
        sleep(1);
    }
    return 0;
}

在这里插入图片描述

从这个代码中可以看出一个现象,那就是获取的线程的id和LWP是不一样的,这也就意味着LWP和id是两个不同的内容,如果将这个数字转换成十六进制,会发现这个数字其实很像一个地址,而事实上这个东西确确实实是一个地址,关于这个地址的概念在后续会进行阐述

线程的终止

那么下面要进行讨论的内容是,线程的终止问题,对于一个线程来说,它如何进行终止?由于线程在Linux中可以看成是一个轻量级的进程,那么这里先回顾一下对于进程的终止概念,对于进程来说想要终止可以进程退出,或是通过return进行退出,那么在这里也可以类比过来,线程也可以进行适当的退出操作,但是在之前的内容中知道,线程是不可以直接进行exit的,线程直接进行exit会导致所有的线程都进行退出,原因就在于exit表示的是进程退出,而不是线程退出,所以任何一个线程调用这个exit函数都会导致整个所有的线程都退出,因为它们共同属于一个进程

那么线程想要单独退出也是有对应的退出接口的,其中一个退出接口就叫做pthread_exit调用

在这里插入图片描述

那么这个接口的作用就是直接对应的线程进行终止掉,所以现在想要终止一个线程,不仅可以调用return语句进行调用,也可以使用对应的调用接口进行退出

线程的返回值问题

下面来到的话题是关于线程的返回值问题,那对于线程的返回值问题来说,首先要搞清楚的是返回值的意义是什么,那这个问题其实很好明白,就是为了让外部的主线程能够获取到对应的返回值,进而进行对应的操作,那下面的问题是如何获取?如果没有及时获取是否会出现类似于进程的僵尸问题?

获取线程的返回值

获取线程的返回值,在Linux系统中存在一个系统调用:

在这里插入图片描述
这个系统调用的作用之一,就是可以获取到指定线程的退出信息,第二个参数是一个二级指针,表明这是一个输出型参数,它想要获取的内容是一个void*类型的参数,而这个参数实际上就是对应在创建线程的时候调用时会返回的值,这个函数可以回收指定线程id的线程,并且把返回值的信息存储到第二个这个输出型参数当中,这样就能完成对于线程的返回值获取

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

class pthread_exit_info
{
public:
    pthread_exit_info(int code, const string &info)
        : _code(code), _info(info)
    {
    }

public:
    int _code;
    string _info;
};

// 新线程
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    cout << threadname << " thread: "
         << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
    cout << threadname << " thread exit" << endl;
    sleep(2);
    return (void *)new pthread_exit_info(0, "exit normal");
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread-1");
    cout << "main thread: "
         << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
    sleep(5);
    
    void* res = nullptr;
    pthread_join(tid, &res);
    pthread_exit_info* exit_info = (pthread_exit_info*)res;
    cout << "线程的退出码是: " << exit_info->_code << " 线程的退出信息是: " << exit_info->_info << endl;
    return 0;
}

在这里插入图片描述

线程的僵尸问题

对于进程来说,如果子进程结束了,但是父进程一直没有对子进程进行资源的回收,那么子进程就会进入僵尸状态,子进程的PCB等资源还在内存中进行占用,需要父进程调用对应的接口进行回收,那对于线程来说当然也会存在这样的问题,但是这个现象不是很明显的观察,所以这里只是会输出这样的观点,确实在操作系统内部会存在线程的僵尸状态的问题,但没有一个明确的规定标准

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

// 新线程
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    cout << threadname << " thread: "
         << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
    cout << threadname << " thread exit" << endl;
    return nullptr;
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread-1");
    cout << "main thread: "
         << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
    sleep(5);

    pthread_join(tid, nullptr);
    return 0;
}

在这里插入图片描述
从上述的现象图中确实可以看到,是存在一个这样的退出的动态过程的,而这个join的过程也是一个阻塞等待的过程,所以在观察现象的时候可以看到,是存在这样的一个从2个线程创建出来到1个线程到没有线程这样的一个过程

补充:

1. 进程等待回收资源的时候,会有一个参数用来获取子进程的退出时的退出信息,包括有信号等等内容,那线程为什么没有?

这个问题其实不难理解,进程退出会有对应的信息是因为父进程不知道子进程的退出情况是什么,可能是正常退出也可能是异常退出,但是在今天看来,主线程并不关心这个问题,原因在于如果新建的进程出现了异常,那么直接整个进程都退出了,所以只要这个线程返回,那么这个线程必然是正常进行退出的,因此从这个角度来讲,确实不需要用来存储异常的信息

2. 如果子线程一直不退出,主线程要一直在这里等待吗?

答案确实是如此,因为这个调用是一个阻塞形式的等待,所以主线程必须卡在这里等这个新线程的返回信息,默认情况下这是阻塞等待,当然也有特殊情况,比如可以对这个新线程设置一个分离状态,表示主线程此时不关心新线程的退出情况了,可以退也可以不退,这个就叫做分离状态

那如何进行线程的分离?

方法也很简单,只需要调用对应的系统调用即可:

在这里插入图片描述

3. 线程的取消

在这里插入图片描述
线程的取消如何理解?简单来说就是当线程正在运行的时候把它进行取消,下面用代码来实现:

#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

class pthread_exit_info
{
public:
    pthread_exit_info(int code, const string &info)
        : _code(code), _info(info)
    {
    }

public:
    int _code;
    string _info;
};

// 新线程
void *ThreadRoutine(void *arg)
{
    const char *threadname = (const char *)arg;
    while (true)
    {
        cout << threadname << " thread: "
             << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
        sleep(1);
    }
    cout << threadname << " thread exit" << endl;
    return (void *)new pthread_exit_info(0, "exit normal");
}

int main()
{
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void *)"thread-1");
    cout << "main thread: "
         << ", pid: " << getpid() << ", tid: " << pthread_self() << endl;
    sleep(2);

    cout << "线程取消" << endl;
    int n = pthread_cancel(tid);
    void *ret = nullptr;
    pthread_join(tid, &ret);
    std::cout << "main thread join done,"
              << " n: " << n << " thread return: " << (int64_t)ret << std::endl;
    return 0;
}

在这里插入图片描述

那在线程被取消后,线程取消函数会返回一个0,而获取线程的退出信息会获取到一个-1,这个-1代表的是一个宏,表示这个线程是因为被线程取消了而退出的:

在这里插入图片描述
基于上述的内容可以看出,线程是可以被取消的,线程是一个死循环,但是依旧不影响它可以被取消,如果这个被取消的线程处于分离状态,那么这个线程被取消后会被操作系统自动回收掉对应的资源

关于原生线程库问题

结束了上面的话题,那么下面一个话题是,关于Linux的原生线程库的问题

在Linux操作系统中,操作系统内部的线程不是真线程,而是用进程模拟的线程,所以Linux系统中不会直接提供对应的任何关于创建线程的系统调用,最多是提供创建轻量级进程的系统调用,但是对于正常的操作系统来说,操作系统是有必要提供对应的线程的,那如何处理这个问题呢?所以就在Linux的系统和用户之间建立起来了一个软件层,这个软件层的作用之一就是负责在底层调用为系统创建对应的轻量级进程的接口,其次在上层返回的时候是一个操作线程的接口,比如说有创建线程,终止线程等等

所以在Linux系统中关于线程的一个解决方案是,使用了一个线程的库来解决的,这个原生线程库的意思就是系统会自带有这个库,如果没有这个库就不能正常运行和线程相关的进程

站在用户层的角度来讲,在用户创建了5个线程后,那么在系统中就会创建5个轻量级进程与之对应,所以在进行查询的时候就能找到5个对应的LWP,对于轻量级进程的管理操作系统自己就可以做到,所以直接复用之前的代码就可以了,但是对于用户来讲却并不是这样,用户想知道现在创建了多少个线程,每一个线程的状态是什么,当前有多少个,某个线程的退出信息是什么,这些该如何获取?从刚才的代码中可以看出可以通过join函数来获取,但是join函数的底层又该从哪里获取这些信息呢?

换句话说,Linux的线程是叫做用户级线程,从字面意思可以看出,这个线程是用户层上的概念,而在内核的角度看只有只有轻量级进程的概念,那操作系统是可以有这个能力把轻量级进程管理起来的,这是操作系统的本职工作,但是对于系统的原生库来说,它又该如何获取信息呢?它该如何进行管理信息呢?

结论是,当操作系统中如果存在了大量的轻量级进程,那么注定对应的原生库也会有自己的方法来进行管理,所以就引出了一个叫做tcb的概念,tcb是对标PCB的一个概念,可以理解成是一个线程描述符的概念,在这个tcb中必然会存在很多的信息,其中就包括有LWP这样的概念,因此换句话说可以理解成,在这样的库中就会存在一些封装的概念,同时还会为每一个线程封装一个简单的tcb,当然这个概念在Linux中是没有的

因此会引入下图这样的概念,类似于文件库,文件库中是可以申请有一段空间作为文件缓冲区,所以线程其实也有这样的能力,而下图中的线程栈就是这样的概念:

主线程的栈通常是在程序启动时由操作系统分配的真实的栈空间,它是在程序的虚拟地址空间中预留的一部分。这个栈空间是在程序的内存映像中分配的,因此可以被认为是在真实的栈上。

而对于其他创建的新线程,它们的栈空间可能由堆空间代替而形成,意味着它们的栈空间是在堆上动态分配的。这种动态分配使得线程栈的大小可以根据需要进行调整,并且可以更灵活地管理内存。

在这里插入图片描述

库是要被加载到共享区的,加载到共享区之后就会在这块区域内维护一个区域,这个区域其实就会分配一段空间,然后把这段空间的地址放到这个库当中,那么在线程进行运行的时候就可以在库中的指针找到这个线程所对应的地址空间了,换句话说,如果未来想要把这个线程进行退出,实际上就是把线程终止,把轻量级进程终止,把线程所维护的这段空间释放掉,就完成了释放的工作

那如果此时创建了很多线程呢?这些线程都会在这个库中进行维护:线程库通常会维护一个数据结构,比如线程表,用于存储每个线程的信息。这个表中的每个条目可能包含线程的标识符(如线程ID)、状态、优先级、调度相关信息,以及指向线程控制块(TCB)或线程栈的指针等。这样的线程表使得线程库可以有效地跟踪和管理每个线程的状态和资源

重要的是,线程控制块(TCB)通常包含了线程的所有信息,包括线程的上下文、寄存器状态、线程栈指针、局部存储等。因此,线程表中存储的指针指向每个线程的TCB,而TCB中又包含了线程的所有信息,使得线程库可以在需要时访问和修改线程的相关信息

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1500476.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

垃圾收集器底层算法

垃圾收集器底层算法 三色标记 在并发标记的过程中&#xff0c;因为标记期间应用线程还在继续跑&#xff0c;对象间的引用可能发生变化&#xff0c;多标和漏标的情况就有可能发生&#xff0c;这里我们引入“三色标记”来给大家解释下把Gcroots可达性分析遍历对象过程中遇到对象…

这些养老难题,只能靠AI来解决了

3 月 5 日刚召开的两会&#xff0c;AI 这个话题妥妥站上了 C 位。不仅政府工作报告首次提出要开展“人工智能”行动&#xff0c;各路科技大佬和人大代表也是围绕着 AI 大模型的技术创新、应用落地和政策法规&#xff0c;展开了热烈积极的建言献策。甚至有互联网大佬建议将人工智…

OKLink2月安全月报| 2起典型漏洞攻击案例分析

在本月初我们发布的2024年2月安全月报中提到&#xff0c;2月全网累计造成损失约1.03亿美元。其中钓鱼诈骗事件损失占比11.76%。 OKLink提醒大家&#xff0c;在参与Web3项目时&#xff0c;应当仔细调研项目的真实性、可靠性&#xff0c;提升对钓鱼网站和风险项目的甄别能力&…

ArcGIS筛选工具:19段SQL示例代码,所有需求一网打尽

一、使用方法 筛选工具(Select_analysis)主要用于从输入要素类或输入要素图层中提取要素&#xff08;通常使用选择或结构化查询语言 (SQL) 表达式&#xff09;&#xff0c;并将其存储于输出要素类中。 以三调图斑为例&#xff0c;图斑中有一个【DLMC】字段&#xff0c;该字段…

MAC测试环境搭建

1 下载pycharm 下载地址&#xff1a;PyCharm&#xff1a;JetBrains 出品的用于数据科学和 Web 开发的 Python IDE 2 安装python3.6.8 下载地址&#xff1a;Index of /ftp/python/3.6.8/ 安装后提示错误 换一种方式&#xff1a;用conda 下载地址&#xff1a;Free Download | …

爬虫入门到精通_框架篇15(Scrapy框架安装)

1 Scrapy安装 Scrapy的安装有多种方式&#xff0c;它支持Python2.7版本及以上或Python3.3版本及以上。下面说明Python3环境下的安装。 Scrapy依赖的库比较多&#xff0c;至少需要依赖库有Twisted14.0,lxml 3.4,pyOpenSSL 0.14。而在不同平台环境又各不相同&#xff0c;所以在安…

警用移动执法远程视频监控方案:安防视频监控系统EasyCVR+4G/5G移动执法仪

一、背景需求 在现代城市管理中&#xff0c;移动执法仪视频监控方案正逐渐成为一种高效、便捷的管理工具。该方案通过结合移动执法仪和视频监控技术&#xff0c;实现了对城市管理现场的实时监控和取证&#xff0c;有效提升了城市管理水平和效率。 移动执法仪作为现场执法的重…

Sora的双重边缘:视频生成的革新与就业的再思考

随着科技的日新月异&#xff0c;人工智能&#xff08;AI&#xff09;和机器学习&#xff08;ML&#xff09;技术如潮水般涌入我们的日常生活&#xff0c;为各个领域带来了翻天覆地的变化。在这一浪潮中&#xff0c;Sora作为一款前沿的AI视频生成工具&#xff0c;凭借其高度逼真…

MySQL--优化(索引--索引失效场景)

MySQL–优化&#xff08;索引–索引失效场景&#xff09; 定位慢查询SQL执行计划索引 存储引擎索引底层数据结构聚簇和非聚簇索引索引创建原则索引失效场景 SQL优化经验 常见的索引失效场景 1、场景准备&#xff1a; 给 tb_user 表创建联合索引&#xff0c;字段为&#xff1…

西部广播电视杂志社《西部广播电视》杂志社2024年第1期目录

聚焦&#xff1a;动漫创作中国故事的突破 从接受美学看奇幻电影如何讲好中国故事——以电影《封神第一部&#xff1a;朝歌风云》为例 郭海芃; 1-4 梦境构筑叙事隐含类型突破——以动画电影《深海》为例 李雯萱; 5-9 国产动画“全龄化”的创作突破——以《长安三万里…

Matlab偏微分方程拟合 | 完整源码 | 视频教程

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《复杂函数拟合案例分享》本专栏旨在提供 1.以案例的形式讲解各类复杂函数拟合的程序实现方法&#xff0c;并提供所有案例完整源码&#xff1b;2.…

一条SQL引起的系统不可用

一.前言 最近在运维系统&#xff0c;系统对客端突然报了403错误&#xff0c;从后台看发现了大量的慢SQL&#xff0c;导致查询超时&#xff0c;仔细分析我从来没见过那么厚颜无耻的SQL&#xff0c;一条SQL语句关联了一个大表&#xff08;6000数据&#xff09;查询了10次。我也很…

【网络连接】ping不通的常见原因+解决方案,如何在只能访问网关时诊断,并修复IP不通的问题

【网络连接】ping不通的常见原因解决方案&#xff0c;如何在只能访问网关时诊断&#xff0c;并修复IP不通的问题 写在最前面网络基础可能的问题、表现以及解决方案如何诊断和解决操作步骤 详细问题描述详细解决方案1. 防火墙或安全软件拦截2. IP配置错误3. 网络设备问题4. 物理…

2024 GoLand激活,分享几个GoLand激活的方案

文章目录 GoLand公司简介我这边使用GoLand的理由GoLand 最新变化GoLand 2023.3 最新变化AI Assistant 正式版GoLand 中的 AI Assistant&#xff1a;_Rename_&#xff08;重命名&#xff09;GoLand 中的 AI Assistant&#xff1a;_Write documentation_&#xff08;编写文档&…

MySql 组合索引的使用

MySql 组合索引的使用 测试Mysql组合索引在不同的查询条件组合下的索引使用情况。当有abc 3个字的的组合索引时&#xff0c;按照MySql 的左匹配原则&#xff0c;abc&#xff0c;ab&#xff0c;a 是满足左匹配原则&#xff0c;肯定是会走索引的&#xff0c;但是其他的场景&…

【PCL】(二十六)自定义条件的欧几里得聚类分割点云

&#xff08;二十六&#xff09;自定义条件的欧几里得聚类分割点云 以下代码实现自定义条件对点进行欧几里得聚类分割。 conditional_euclidean_clustering.cpp #include <pcl/point_types.h> #include <pcl/io/pcd_io.h> #include <pcl/console/time.h>#…

鞋服品牌怎样合理把控订货深度和宽度

在鞋服品牌的运营管理中&#xff0c;订货深度和宽度是两个至关重要的概念。订货深度指的是某一款式或规格的产品数量&#xff0c;而订货宽度则代表品牌所涵盖的产品种类和款式。合理把控订货深度和宽度对于品牌的库存管理、销售情况以及顾客满意度都有着深远的影响。本文将探讨…

【Linux】 yum —— Linux 的软件包管理器

Linux 的软件包管理器 yum yum 是什么什么是软件包查看软件包 yum 命令行工具yum 配置文件yum 凭什么可以支持下载呢&#xff1f;yum 生态yum 社区yum 的故障排除和资源支持yum 的持续集成和持续交付 yum 是什么 Yum&#xff08;Yellowdog Updater Modified&#xff09;是一个…

洛谷 P8816 [CSP-J 2022] 上升点列(T4)

目录 题目传送门 算法解析 最终代码 提交结果 尾声 题目传送门 [CSP-J 2022] 上升点列 - 洛谷https://www.luogu.com.cn/problem/P8816 算法解析 k 0 且 xi, yi 值域不大时&#xff0c;这题是非常简单的 DP&#xff0c;类似「数字三角形」。 记 dp(x,y) 为「以 (x,y) …

GaussDB(DWS)运维利刃:TopSQL工具解析

在生产环境中&#xff0c;难免会面临查询语句出现异常中断、阻塞时间长等突发问题&#xff0c;如果没能及时记录信息&#xff0c;事后就需要投入更多的人力及时间成本进行问题的定位和解决&#xff0c;有时还无法定位到错误出现的地方。在本期《GaussDB(DWS)运维利刃&#xff1…