【Linux】多线程:POSIX库、线程管理、线程ID

news2024/9/20 18:31:46

目录

一、POSIX线程库

二、线程ID

三、动态库加载

四、再谈线程ID


一、POSIX线程库

原生库:指的是操作系统自带的库,如POSIX线程库,在类Unix系统中通常是原生支持的。这些库是操作系统的一部分,提供了系统级的线程管理功能。

【了解】兼容性和标准化

  • POSIX 标准:POSIX(Portable Operating System Interface)是一个由 IEEE 制定的标准,旨在提供一个一致的操作系统接口,以提高程序的可移植性。POSIX 线程库(pthreads)是 POSIX 标准的一部分,它定义了一组线程相关的 API。Linux 提供这一标准的实现,以确保与其他遵循 POSIX 标准的系统(如 UNIX 和其他类 UNIX 系统)兼容,使得在不同操作系统之间移植代码变得更加容易。

  • 跨平台开发:许多应用程序和库依赖于 POSIX 标准,以便在多个操作系统上进行开发和运行。提供 POSIX 原生库使得这些应用程序可以更容易地移植到 Linux 平台,同时保持一致的接口和行为。

通过前面的学习我们了解到在Linux操作系统中,实际上并不存在真实的线程,Linux内核是以轻量级进程(LWP)这一机制来实现的。在Linux中,线程并不是像在一些其他操作系统中那样的独立实体。相反,线程实际上是被实现为轻量级进程(LWP)。每个线程都被内核视为一个独立的LWP。

同时,Linux操作系统也为创建线程提供了一系列的系统调用,如clone函数,供操作者来控制线程。我们对此仅作了解。

clone 系统调用的基本原型

int clone(unsigned long flags, void *child_stack, int newtls, int *parent_tid, int *child_tid);

使用场景:

  • 进程创建:通过设置适当的 flagsclone 可以用来创建一个新的进程(如通过 CLONE_VM 标志共享内存空间)。

  • 线程创建:通过设置合适的标志(如 CLONE_VMCLONE_FSCLONE_FILES 和 CLONE_SIGHAND),clone 可以用来创建线程,这些线程与父进程共享大部分资源。

我们知道,不同的操作系统中,实现线程的方式并不一致,这就造成了在Linux系统中使用系统调用编写的多线程程序在其他操作系统中可能并不适用。同时,通过clone函数我们可以发现,此类系统调用函数的使用较为复杂,在使用时需要程序员自身为线程的手动分配栈等内存空间并进行管理,这无疑加大了编程的复杂度。而POSIX库的出现恰好解决了这一系列的问题。

  • 简化开发:POSIX 线程库提供了一个一致的编程接口,封装了线程创建、管理、同步等操作。这使得开发者可以使用统一的 API 来处理线程相关任务,而不必依赖于特定操作系统的特性或接口。

  • 功能丰富:POSIX 线程库包含了多种功能,如线程创建、线程同步(互斥锁、条件变量)、线程局部存储等。这些功能的标准化和一致性使得编写多线程程序变得更加高效和可靠。

  • 直接支持:Linux 内核提供了对 POSIX 线程库的原生支持,通过 clone 系统调用实现线程的创建和管理。这样,线程操作可以直接由内核处理,减少了额外的抽象层,从而提高了性能。

  • 内核和用户空间的分离:Linux 的设计哲学是将内核和用户空间的功能分开,POSIX 线程库为用户空间提供了一套清晰的线程管理接口,而内核通过 clone 等系统调用来实现这些功能。这样的设计使得系统的功能划分更加明确,易于维护和扩展。

简而言之,POSIX线程库封装了一系列创建线程的系统调用,并为用户提供了更为简洁的编程接口,隐藏了诸如栈管理和线程资源的共享问题等底层细节,同时增加了程序的可移植性,减少了多线程编程的复杂度。 

二、线程ID

在进程章节的学习中,我们知道每个进程都有一个自己唯一的PCB(在Linux系统中是task_struct),也就是进程控制块。而操作系统为了便于对进程进行唯一标识,则为每个PCB分配了一个进程描述符——PID,也就是进程ID,存储在每个进程对应的PCB中。我们可以使用ps -ajx命令组合来查看系统中正在运行的进程。

那既然进程作为一个独立的单位,拥有自身的唯一标识。那么线程作为分派和调度的独立单位,是否也一样拥有自身唯一的标识符呢?答案是肯定的。

在Linux操作系统中,线程实际上是“轻量级进程”,依然属于进程的范畴,task_struct是Linux内核中用于表示和管理每个进程(包括线程)的核心数据结构。每个进程和线程都有一个task_struct实例,它包含了进程或线程的所有必要信息。

虽说线程实际上是轻量级进程,但是线程存在于进程的地址空间之中,他无法拥有独立的进程ID,而是拥有独立的线程ID。而线程ID在Linux内核和POSIX库中的表示又是各不相同的。这也正对应了Linux的核心思想:内核态与用户态的分离

  • 内核态:在内核态中,线程ID和进程ID的管理是由内核直接处理的。内核使用线程ID来跟踪和调度线程,同时为每个轻量级进程(线程)分配一个唯一的ID。这个ID在内核内部是唯一的,并用于各种内部操作,如上下文切换和调度。

  • 用户态:在用户态中,POSIX线程库(pthread库)提供了一种线程的抽象,允许程序员在应用层进行线程管理。POSIX线程库中的线程ID(如pthread_t类型)与内核中的线程ID不同。POSIX线程ID是一个用户级的抽象,用于简化线程的管理和操作。

接下来,我们先见一见线程:

【注意:1、要使用这些函数库,要通过引入头文<pthread.h>; 2、链接这些线程函数库时要使用编译器命令的“-lpthread”选项】

pthread_create函数是POSIX线程(pthreads)库中的一个核心函数,用于在用户空间创建新的线程。这个函数的主要功能是启动一个新线程,使其并行执行指定的函数。

函数原型

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine)(void*), void *arg);

参数说明

  1. pthread_t *thread

            指向pthread_t类型的指针,用于存储新创建线程的ID。pthread_t是一个线程标识符类型,用于在后续操作中引用该线程。
  2. const pthread_attr_t *attr

            指向pthread_attr_t类型的指针,指定线程的属性。可以为NULL,这时线程将使用默认的属性。如果需要自定义线程的属性,可以通过pthread_attr_init函数初始化pthread_attr_t结构体,并设置相关属性。
  3. void *(*start_routine)(void*)

            指向函数的指针,这个函数将在线程中执行。该函数的参数是一个void*类型的指针,返回值也是void*类型。start_routine函数是线程的入口点。
  4. void *arg

            传递给start_routine函数的参数。arg是一个void*类型的指针,可以传递任意类型的数据。在线程开始执行时,arg将被作为参数传递给start_routine函数。

返回值

  • 成功:返回0。
  • 失败:返回一个错误码,指示失败的原因。
  • pthread_ create 函数会产生一个线程 ID, 存放在第一个参数指向的地址中。
  • 前面讲的线程 ID 属于进程调度的范畴。 因为线程是轻量级进程, 是操作系统
    调度器的最小单位, 所以需要一个数值来唯一表示该线程。
  • pthread_ create 函数第一个参数指向一个虚拟内存单元, 该内存单元的地址即为新创建线程的线程 ID, 属于 NPTL 线程库的范畴。 线程库的后续操作,就是根据该线程 ID 来操作线程的。
  • 线程库 NPTL 提供了 pthread_ self 函数, 可以获得线程自身的 ID:
#include <pthread.h>

pthread_t pthread_self(void);
  • 返回值:返回一个 pthread_t 类型的线程标识符,表示当前调用线程的唯一标识符。
  • pthread_t 到底是什么类型呢? 取决于实现。 对于 Linux 目前实现的 NPTL 实现而言, pthread_t 类型的线程 ID, 本质就是一个进程地址空间上的一个地址。
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

// 线程函数
void *thread_function(void *arg)
{
    printf("%s Process ID is: %d\n", (const char *)arg, getpid());
    printf("%s Thread ID is: %ld\n", (const char *)arg, pthread_self());
    sleep(5);
    pthread_exit(NULL); // 结束线程
}

int main()
{
    pthread_t thread;
    const char *arg = "Thread -1";

    // 创建线程
    int ret = pthread_create(&thread, NULL, thread_function, (void *)arg);
    if (ret != 0)
    {
        fprintf(stderr, "Error creating thread: %d\n", ret);
        exit(EXIT_FAILURE);
    }
    printf("My Process ID is: %d\n", getpid());
    printf("Main Thread ID is: %ld\n", pthread_self());
    // 等待线程结束,回收线程资源
    pthread_join(thread, NULL);

    printf("Thread has finished.\n");

    return 0;
}

在上述程序中,我们创建了一个线程,让该线程打印出该线程所属的进程和它的线程ID。在主线程中,也打印出主线程所属的进程和线程ID。在程序执行的过程中,我们可以通过如下命令来观察线程的状态:

ps -aL  //获得系统中的所有进程和线程的详细列表

我们能够观察到,这两个线程同属于一个进程之中,并且有一个线程(轻量级进程)的ID与进程的ID相同,这就是我们所说的“主线程”,主线程并不需要我们手动创建,当一个进程启动时,操作系统会自动创建一个线程,这个线程被称为“主线程”或“初始线程”。而ID为812435的线程则是我们通过pthread_create系统调用所创建的额外线程。

但同时,我们也印证了之前的说法,程序中所打印出的线程ID与内核中的线程ID并不相同。这是因为Linux中的线程是用户级线程,由POSIX库对其进行管理,所以我们在程序中所打印出来的线程ID实际是库给我们所分配的线程唯一标识。实际上,用户级线程ID是对线程库管理的一个抽象,而内核中的线程ID(LWP)则是真正用于系统调度和管理的标识。

三、动态库加载

在使用动态库(也称为共享库)时,动态库的加载和方法解析涉及到几个重要的步骤:

1. 动态库的加载

动态库的加载通常在程序运行时发生,这一过程包括以下几个步骤:

  • 延迟加载(Lazy Loading):动态库通常是在程序运行时根据需要进行加载的。这意味着只有在程序首次调用动态库中的函数时,动态库才会被实际加载到内存中。在 Linux 系统中,动态库加载通常是由 dlopen 函数完成的,该函数会将指定的动态库加载到进程的地址空间中。

  • 立即加载(Eager Loading):某些情况下,动态库可能在程序启动时就被加载。这通常由编译器在程序启动时通过运行时链接器(如 ld-linux.so)自动处理,或者由程序员在链接时指定。此时,动态库在程序启动时就已经被映射到内存中。

2. 页表映射

  • 内存映射:在动态库被加载到内存中时,操作系统会将动态库的内容映射到进程的虚拟地址空间。这是通过操作系统的虚拟内存管理机制实现的。操作系统将动态库的文件映射到进程的虚拟地址空间中,并更新进程的页表以确保对动态库内存的访问是有效的。

  • 动态链接:动态库的加载和页表映射涉及到动态链接过程。在 Linux 上,这个过程由动态链接器 ld.so 完成。它负责将动态库的符号(即库中的函数和变量)解析到实际的内存地址。

3. 方法的解析

  • 符号解析:当程序调用动态库中的函数时,程序需要知道这些函数在内存中的实际地址。动态链接器会解析动态库中的符号,找出每个符号(如函数或变量)在内存中的地址。这些符号信息通常保存在动态库的符号表中。

  • 符号表:动态库的符号表包括所有导出的函数和变量的信息。动态链接器会在动态库加载时处理这些符号表,并将它们与程序中对应的引用进行匹配。程序中的每个符号引用(例如,函数调用)会被替换为实际的内存地址。

  • 重定位:动态链接器会更新程序的内部结构(如跳转表或函数指针)以使用动态库中函数的实际地址。这使得程序可以在运行时正确地调用动态库中的函数。

也就是说,在我们使用pthread_create函数创建线程之前,排除其他情况,这时在该进程的虚拟地址空间中并不包含线程库,它依然暂存在磁盘中。该程序启动时,程序的代码和数据从磁盘中加载进内存当中,映射到操作系统为该进程所创建的虚拟地址空间当中。当使用pthread_create方法时,此时该进程的地址空间中并没有pthread库。所以此时pthread库将会从磁盘加载至内存当中,并通过页表与进程的虚拟地址空间建立映射。因为函数是有地址的,所以在该进程的地址空间内,可以成功找到pthread_create函数所处的位置,进而执行该方法。

四、再谈线程ID

POSIX库是用什么手段来确保线程ID的唯一性的呢?POSIX库所生成的线程ID本质就是一个进程地址空间上的一个虚拟地址!而地址本身就具有唯一性!!!

实际上,pthread库中维护着用户级线程的基本属性。每个线程的属性集合(即pthread_attr_t 结构体)在地址空间中占用一块内存空间。而用户级线程ID实际上是pthread_attr_t 结构体的起始地址。因此,用户级线程ID也确保了它的唯一性!此后,当我们想要获取线程内部的属性时,只需要拿到该线程的TID—即线程控制块的起始地址,即可对线程控制块的内部属性进行访问!

 

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

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

相关文章

基于人工智能的植物病害检测系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 植物病害的早期检测对于农业生产至关重要&#xff0c;它有助于及时采取措施防止病害扩散&#xff0c;减少作物损失。通过人工智能技术…

css问题:display:flex布局+justify-content: space-between; 最后一行不能左对齐

解决方法1&#xff1a; display: flex;margin: 10px var(--leftRight); --leftRight&#xff1a; 动态计算一行减去item的宽度后剩下的间距 解决方法2&#xff1a;网格布局 display: grid;grid-template-columns: repeat(5, 1fr);margin: 10px auto; 完整代码&#xff1a; &l…

C++ 继承学习笔记

1.继承概念 继承 (inheritance) 机制是面向对象程序设计 使代码可以复用 的最重要的手段&#xff0c;它允许程序员在 保 持原有类特性的基础上进行扩展 &#xff0c;增加功能&#xff0c;这样产生新的类&#xff0c;称派生类。继承 呈现了面向对象 程序设计的层次结构 &#xf…

奥威让您更懂现金流情况

企业现金流一旦出了问题都是大问题&#xff0c;会直接影响到企业的日常运作&#xff0c;甚至直接关系到企业能不能继续存活&#xff0c;因此现金流量表是企业财务分析中重要报表之一&#xff0c;也是企业监控财务监控情况的重要手段之一。那么这么重要的一份现金流量表该怎么做…

科研绘图系列:R语言折线图(linechart plots)

文章目录 介绍加载R包导入数据数据预处理画图组合图形介绍 在R语言中,折线图(Line Plot)是一种常用的数据可视化类型,用于展示数据随时间或有序类别变化的趋势。折线图通过连接数据点来形成一条或多条线,这些线条可以清晰地表示数据的变化方向、速度和模式。 加载R包 k…

基于Spring Boot的宠物领养系统的设计与实现

基于Spring Boot的宠物领养系统的设计与实现 springboot138宠物领养系统的设计与实现 摘 要 如今社会上各行各业&#xff0c;都在用属于自己专用的软件来进行工作&#xff0c;互联网发展到这个时候&#xff0c;人们已经发现离不开了互联网。互联网的发展&#xff0c;离不开一…

第 1 章:原生 AJAX

原生AJAX 1. AJAX 简介 AJAX 全称为 Asynchronous JavaScript And XML&#xff0c;就是异步的 JS 和 XML。通过 AJAX 可以在浏览器中向服务器发送异步请求&#xff0c;最大的优势&#xff1a;无刷新获取数据。AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准组合在一…

JavaWeb案例

环境搭建 先创建好数据库&#xff0c;建表并插入数据 create database talis; use talis;-- 部门管理 create table dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null com…

Springboot整合【Kafka】

1.添加依赖 在pom.xml文件中添加以下依赖&#xff1a; <!-- 进行统一的版本管理--><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.3.3</version>&l…

【全网最全】2024年数学建模国赛A题30页完整建模文档+成品论文+代码+可视化图表等(后续会更新)

您的点赞收藏是我继续更新的最大动力&#xff01; 一定要点击如下的卡片&#xff0c;那是获取资料的入口&#xff01; 2024年高教社杯数学建模国赛A题“板凳龙”闹元宵&#xff1a;建立舞龙队的运动轨迹和速度的空间几何、运动学和优化模型 本文文章较长&#xff0c;建议先看…

ardupilot开发 --- MQTT 篇

原图&#xff1a;ardupilot-onboardComputer-4Glink-console.drawio 白嫖党请点赞、收藏、关注 你说在一起要算命 前言参考文献 前言 为什么在ardupilot开发过程中要用到MQTT &#xff1f; 客户要求向他们的指挥中心平台推送视频流和飞控数据&#xff0c;即要将图数传数据推送给…

代码随想录:96. 不同的二叉搜索树

96. 不同的二叉搜索树 class Solution { public:int numTrees(int n) {int dp[30]{0};//由i个结点组成的二叉搜索树有多少种dp[0]1; for(int i1;i<n;i)for(int j0;j<i;j)//j表示根节点左子树有j个结点dp[i]dp[j]*dp[i-j-1];//对根节点左右子树结点数量遍历//数量有左子树…

【计算机网络】TCP连接如何确保传输的可靠性

一、确保可靠传输的机制 TCP&#xff08;传输控制协议&#xff09;是一种面向连接的、提供可靠交付的、面向字节流的、支持全双工的传输层通信协议 1、序列号 seq TCP头部中的序号&#xff0c;占32位&#xff08;4字节&#xff09;&#xff1b; 发送方给报文段分配一个序列号&a…

CSS中 特殊类型的选择器 伪元素如何使用

一、什么是伪元素 在 CSS 中&#xff0c;伪元素是一种特殊类型的选择器&#xff0c;它允许你为元素的特定部分添加样式&#xff0c;而这些部分在 HTML 文档中并不实际存在。伪元素通常用于创建装饰性效果&#xff0c;如添加边框、背景、阴影等&#xff0c;而不需要额外的 HTML…

PHPJWT的使用

今天得空整理整理JWT的代码 首先&#xff0c;我们得知道什么是JWT&#xff1f; JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC7519&#xff09;&#xff0c;用于在网络应用环境中安全地传输声明信息。它是一种紧凑的、URL安全的令牌格式&#xff0…

(一)使用Visual Studio创建ASP.NET Core WebAPI项目

1.创建webAPI项目 选择ASP.NET Core Web API项目模版&#xff08;基于.Core框架可以支持多种系统环境&#xff0c;所以我们选择.Core框架&#xff09;&#xff0c;点下一步。 2.项目名称 项目名称设置为&#xff1a;CoreWebAPI&#xff0c;点下一步 3.选择框架 选择.NET6.0框…

分类预测|基于黑翅鸢优化轻量级梯度提升机算法数据预测Matlab程序BKA-LightGBM多特征输入多类别输出 含对比

分类预测|基于黑翅鸢优化轻量级梯度提升机算法数据预测Matlab程序BKA-LightGBM多特征输入多类别输出 含对比 文章目录 一、基本原理BKA&#xff08;Black Kite Algorithm&#xff09;的原理LightGBM分类预测模型的原理BKA与LightGBM的模型流程总结 二、实验结果三、核心代码四、…

IP学习——twoday

双层Vlan标签 路由器常用命令&#xff1a; 查看当前端口&#xff0c;路由等的信息和配置&#xff1a;display this 查看当前路由器的所有信息&#xff1a; display current-configuration 查看当前路由器的指定信息&#xff1a; display current-configuration | include ip a…

HTML第一课 语法规范与常用标签

目录 ◆ HTML 语法规范 ◆ HTML 常用标签 4.2 标题标签 4.3 段落和换行标签 4.4文本格式化标签 4.5<div>和<span>标签 4.6图像标签和路径 4.7超链接标签 1.外部链接 2.内部链接 3.空链接 4.下载链接 5.锚点链接 ◆ HTML 中的注释和特殊字符​编辑 ◆ HTML 语…

Redis中String类型的基本命令

文章目录 一、String字符串简介二、常见命令setgetmgetmsetsetnxincrincrbydecrdecrbyincrbyfloatappendgetrangesetrangestrlen 三、命令小结四、字符串内部编码五、String典型使用场景1. 缓存(Cache)功能2. 计数功能3. 共享会话&#xff08;Session&#xff09;4. 手机验证码…