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

文章目录

  • 线程的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中又包含了线程的所有信息,使得线程库可以在需要时访问和修改线程的相关信息

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

设计模式学习笔记 - 设计原则 - 8.迪米特法则(LOD)

前言 迪米特法则&#xff0c;是一个非常实用的原则。利用这个原则&#xff0c;可以帮我们实现代码的 “高内聚、松耦合”。 围绕下面几个问题&#xff0c;来学习迪米特原则。 什么是 “高内聚、松耦合”&#xff1f;如何利用迪米特法则来实现 高内聚、松耦合&#xff1f;哪些…

【Python】FastAPI 项目创建 与 Docker 部署

文章目录 前言&需求描述1. 本地FastAPI1.1 Python 环境准备1.2 本地 Pycharm 创建FastAPI项目 2. Python FastAPI 部署2.1 服务器配置Python环境2.2.1 下载与配置Git、Pyenv等工具2.2.2 下载与配置Python 2.2 FastAPI 打包成镜像2.2.1 项目准备所需环境文件2.2.2 编写Docke…

Java基于SpringBoot的在线文档管理系统的设计与实现论文

摘 要 随着科学技术的飞速发展&#xff0c;社会的方方面面、各行各业都在努力与现代的先进技术接轨&#xff0c;通过科技手段来提高自身的优势&#xff0c;在线文档管理当然也不能排除在外。在线文档管理系统是以实际运用为开发背景&#xff0c;运用软件工程原理和开发方法&am…

合并两个有序链表

题目 题目链接 合并两个排序的链表_牛客题霸_牛客网 题目描述 代码实现 class Solution { public:/*** 代码中的类名、方法名、参数名已经指定&#xff0c;请勿修改&#xff0c;直接返回方法规定的值即可** * param pHead1 ListNode类 * param pHead2 ListNode类 * return …

1分钟学会Python字符串前后缀与编解码

1.前缀和后缀 前缀和后缀指的是&#xff1a;字符串是否以指定字符开头和结尾 2.startswith() 判断字符串是否以指定字符开头&#xff0c;若是返回True&#xff0c;若不是返回False str1 "HelloPython"print(str1.startswith("Hello")) # Trueprint…

Python 微信自动化工具wxauto开发系列01(2024年3月可用 支持3.9最新微信)

下载链接&#xff1a;GitHub - cluic/wxauto: Windows版本微信客户端&#xff08;非网页版&#xff09;自动化&#xff0c;可实现简单的发送、接收微信消息&#xff0c;简单微信机器人d 推荐大家在github下载&#xff1a; git clone https://github.com/cluic/wxauto.git cd w…

数据结构——基本术语和概念

目录 1.数据 2.数据元素 3.数据项 4.数据对象 数据元素与数据对象 5.数据结构 1.逻辑结构 逻辑结构的种类 划分方式1 1.线性结构 2.非线性结构 ​ 划分方式2——四类基本逻辑结构 2.物理结构&#xff08;存储结构&#xff09; 1.顺序存储结构 2.链接存储结构 3…

【贪心算法】Leetcode 455.分发饼干 376. 摆动序列 53. 最大子数组和

【贪心算法】Leetcode 455 分发饼干 376. 摆动序列【规律很多】53. 最大子数组和 455 分发饼干局部最优推全局最优&#xff1a;尽量用大饼干去满足大胃口的小朋友 376. 摆动序列【规律很多】思想&#xff1a;注意考虑一个坡度留首尾两个点、平坡、首尾 53. 最大子数组和【好思想…

SD-WAN助力企业数据传输安全

随着企业网络需求的不断增长&#xff0c;SD-WAN成为企业网络组网的首选方案&#xff0c;能够实现多种网络拓扑结构的无缝连接&#xff0c;其中包括总部-分支、总部-分支-数据中心、总部-数据中心、总部-分支-云服务等。如何确保企业数据在传输过程中的安全性成为企业关注的重要…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的植物病害检测系统(Python+PySide6界面+训练代码)

摘要&#xff1a;开发高效的植物病害检测系统对于提升农业生产效率和作物健康管理意义重大。本篇博客详细阐述了如何运用深度学习技术构建一个植物病害检测系统&#xff0c;并提供了完整的实现代码。该系统基于先进的YOLOv8算法&#xff0c;对YOLOv7、YOLOv6、YOLOv5进行了性能…

用指针数组完成单词倒排

描述 对字符串中的所有单词进行倒排。 说明&#xff1a; 1、构成单词的字符只有26个大写或小写英文字母&#xff1b; 2、非构成单词的字符均视为单词间隔符&#xff1b; 3、要求倒排后的单词间隔符以一个空格表示&#xff1b;如果原字符串中相邻单词间有多个间隔符时&…

更换个人开发环境后,pycharm连接服务器报错Authentication failed

原因&#xff1a;服务器中更换个人开发环境后&#xff0c;密码变了。 解决&#xff1a;在pycharm中修改服务器开发环境密码即可。 1 找到Tools-Depolyment-Configuration 2 点击SSH Configuration后的省略号 3 修改这里面的Password即可

SVN教程-SVN的基本使用

SVN&#xff08;Apache Subversion&#xff09;是一款强大的集中式版本控制系统&#xff0c;它在软件开发项目中扮演着至关重要的角色&#xff0c;用于有效地跟踪、记录和管理代码的演变过程。与分布式系统相比&#xff0c;SVN 的集中式架构使得团队能够更加协同地进行开发&…

Docker将本地的镜像上传到私有仓库

使用register镜像创建私有仓库 [rootopenEuler-node1 ~]# docker run --restartalways -d -p 5000:5000 -v /opt/data/regostry:/var/lib/registry registry:2[rootopenEuler-node1 ~]# docker images REPOSITORY TAG IMAGE…

DataIntegrityViolationException异常产生原因及解决方案

DataIntegrityViolationException异常产生原因及解决方案 01 异常的发生场景 在我新写了一个接口之后出现的 //org.springframework.dao.DataIntegrityViolationException日志报错的意思是参数设置了一个错误的值 02 异常的产生及其原因 我最开始认为是MySQL数据库表设计…

【学习总结】什么是DoS和DDoS

[Q&A] 什么是DoS DoS 是 “Denial of Service”&#xff08;拒绝服务&#xff09;的缩写&#xff0c;它是一种网络攻击方式&#xff0c;其目的是使目标计算机或网络资源无法为合法用户提供正常的服务。通过向目标系统发送大量请求、消耗其带宽、处理器或内存等资源&#…

网络安全: Kali Linux 使用 nmap 扫描目标主机

目录 一、实验 1.环境 2. Kali Linux (2024.1) 使用 namp 扫描目标主机 3.Kali Linux (2024.1)远程登录 Windows Server 4.Kali Linux (2024.1) 使用crunch字典工具 5.Kali Linux (2024.1)使用hydra密码工具 6.Kali Linux (2022.3) 通过SSH端口获取 Ubuntu 密码 二、问题…

备考2024年北京高考数学:20114~2023十年选择题练习和解析

距离2024年高考还有三个月的时间&#xff0c;如何用三个月的时间再提高北京数学高考的成绩&#xff1f;吃透历年真题以及背后的知识点是行之有效的方法 之一。 今天我们来看一下2014-2023年的北京市高考数学的选择题&#xff0c;从过去十年&#xff08;2014-2023&#xff09;的…

SandBox中的JavaAgent技术

8.1 JavaAgent Java Agent 是一种强大的技术&#xff0c;在运行时动态修改已加载类的字节码&#xff0c;为应用程序注入额外的功能和行为。 JDK 1.5 支持静态 Instrumentation&#xff0c;基本的思路是在 JVM 启动的时候添加一个代理&#xff08;javaagent&#xff09;&#…

【python debug】python常见编译问题解决方法_2

序言 记录python使用过程中碰到的一些问题及其解决方法上一篇&#xff1a;python常见编译问题解决方法_1 1. PermissionError: [Errno 13] Permission denied: ‘/lostfound’ 修改前&#xff1a; 修改后&#xff08;解决&#xff09;&#xff1a; 此外&#xff0c;可能文件夹…