【Linux —— 理解pthread库和底层逻辑】

理解pthread库

pthread库是一个动态库

使用下面指令可以查找的系统目录下的库信息

ls /lib/x86_64-linux-gnu/libpthread.so.0 -l

在这里插入图片描述
可以看到,libpthread.so.0是一个动态库,而动态库是在运行时由程序加载。

pthread 库实现了POSIX标准下的线程管理功能,它提供了**创建、管理、同步和销毁线程的函数。**这些函数允许程序在一个进程中同时执行多个任务,即多线程操作。 这些函数都是对系统调用的封装,提供高效、标准化的多线程编程接口,它通过动态加载和链接机制,不仅节省了系统资源,还提供了灵活的升级路径。在使用pthread库时,程序可以享受标准线程操作接口的便利,而不必关心底层复杂的线程管理和调度细节。

底层逻辑

1.线程创建的目标
pthread_create的主要任务就是在一个进程中创建一个新的线程,新的线程共享地址空间和部分资源。

2.使用clone() 系统调用
在Linux中,线程的创建的核心就是使用clone()clone()fork()相似,但是比后者更加的灵活,其可以控制新线程和父进程共享的资源和属性。

clone() 系统调用的签名为:

pid_t clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ...
            pid_t *ptid, void *tls, pid_t *ctid);

参数:

  • fn: 新线程的执行函数指针。
  • child_stack: 指向新线程的栈顶,用于在新线程中执行函数。
  • flags: 指定新线程与父线程共享哪些资源(如内存、文件描述符等)。
  • arg: 传递给 fn 的参数。
  • 还有一些可选的参数(如 tls, ptid, ctid),用于更细粒度的控制新线程的特性。

线程创建的流程:
线程创建的大体流程如下:

  1. 准备线程属性:
  • pthread_create 首先会根据传入的线程属性(如栈大小、调度策略)准备必要的资源。如果没有指定,使用默认值。
  • 例如,默认情况下,新线程会继承进程的地址空间、文件描述符表、信号处理器等。
  1. 设置栈空间:
  • 为新线程分配栈空间,通常通过映射新的内存区域来完成(使用 mmap() 或现有栈空间)。
  • 栈空间是线程私有的,即每个线程都有自己的栈,以避免不同线程之间的数据混淆。
  1. 调用 clone():
  • pthread_create 通过 clone() 系统调用创建新线程clone() 创建一个新的轻量级进程(LWP),它与调用者共享资源(如地址空间),但有自己的栈和执行上下文。
  • 通过 flags 参数指定哪些资源要共享,常见的标志包括:
    • CLONE_VM: 共享内存空间。
    • CLONE_FS: 共享文件系统信息。
    • CLONE_FILES: 共享文件描述符表。
    • CLONE_SIGHAND: 共享信号处理。
  1. 在新线程中执行函数:
  • clone() 成功后,新线程开始执行 fn 函数,并将传递的 arg 作为参数。
  • 新线程执行函数 fn,直到它返回或显式调用 pthread_exit。
  1. 线程 ID 的管理:
    pthread_create 返回时,会将新创建线程的 ID (pthread_t) 传给调用者。这通常是一个整数(或指针),在系统中唯一标识该线程。
    内核会跟踪这个线程 ID 以及它的相关信息(如状态、栈指针等)。

LWP

LWP是Linux系统中一个重要的概念,一般用来描述系统内核的线程的实现,也是一种轻量化的进程。
 轻量级进程(LWP) 是内核调度的最小单位,LWP通常与用户线程(如使用pthread库创建的线程)一一对应。每个用户线程对应一个LWP,LWP由内核调度和管理。
通过 clone() 系统调用可以创建LWP。clone()允许指定与父进程共享的资源,创建出的子进程就是一个LWP。
 LWP的实现是通过 task_struct 结构体。每个LWP都有一个 task_struct 实例,该结构体包含了调度所需的所有信息,如进程ID(PID)、状态、调度策略等。

pthread_t

pthread_t的概念

pthread_t 是 Pthreads库中的一个数据类型,用来标识一个线程。

当调用pthread_create()时,Pthreads库会为新线程分配一个pthread_t标识符。这个标识符通常在内部对应一个轻量级进程的ID(LWP ID)。

pthread_t 是一个用户级别的标识符。

pthread_t 的实现

pthread_t 的具体实现取决于操作系统和平台。在 Linux 上,通常 pthread_t 被定义为一个 unsigned long int,即一个无符号长整型。这种类型能够唯一标识一个线程。

实际上,pthread_t 在内部可能是一个线程控制块(TCB)的指针,或者是一个可以唯一标识线程的 ID。在 Linux 上,它可能会被直接映射到轻量级进程(LWP)的 ID,或者映射到某种形式的索引结构。

pthread_t 与 LWP 的关系

  • pthread_t 是用户空间的标识符,用户通过它操作线程。每个 pthread_t 对应一个内核中的轻量级进程(LWP)。
  • 当用户调用 pthread 库的函数操作pthread_t 时,底层会通过系统调用与内核交互,对应操作LWP

独立的栈空间

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

int g_val = 100;

std::string ToHex(pthread_t tid)
{
    char ch[128];
    snprintf(ch, sizeof(ch), "0x%lx", tid);
    return ch;
}

void *threadRun(void *arg)
{
    std::string name = static_cast<const char *>(arg);
    while (1)
    {
        std::string tid = ToHex(pthread_self());
        std::cout << "I am new thread, my id = " << tid << " val = " << g_val << " &val = " << &g_val << std::endl;
        sleep(1);
    }
}

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

    while (1)
    {
        std::cout << "I am main thread, my val = " << g_val << " &val = " << &g_val << std::endl;
        sleep(1);
    }

    return 0;
}

声明全局变量,分别通过主新线程打印变量值和地址。
在这里插入图片描述
主新线程的g_val值和地址都一样。

那如果在声明变量的时候加上__thread呢?

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

__thread int g_val = 100;		//加上了__thread

std::string ToHex(pthread_t tid)
{
    char ch[128];
    snprintf(ch, sizeof(ch), "0x%lx", tid);
    return ch;
}

void *threadRun(void *arg)
{
    std::string name = static_cast<const char *>(arg);
    while (1)
    {
        std::string tid = ToHex(pthread_self());
        std::cout << "I am new thread, my id = " << tid << " val = " << g_val++ << " &val = " << &g_val << std::endl;
        sleep(1);
    }
}

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

    while (1)
    {
        std::cout << "I am main thread, my val = " << g_val << " &val = " << &g_val << std::endl;
        sleep(1);
    }

    return 0;
}

在这里插入图片描述

主新线程的g_val值和地址都不一样,证明不太线程有其独立的栈空间。

管理

pthread_t 是一个用户级别的标识符,而LWP是内核级别的标识符。

  • 内核管理:
     无论是进程还是线程,内核都使用 task_struct 结构体来进行管理。task_struct 是进程和线程在内核中的核心数据结构,包含了所有的管理信息。
  • 用户空间管理:
     pthread 库在用户空间使用自己的数据结构(如 pthread_t 和 TCB)来抽象和管理线程。这些结构为用户提供了线程操作的接口,同时通过系统调用将操作委托给内核。最终,内核中的 task_struct 结构体管理实际的线程调度和执行。

在这里插入图片描述
&emsp因此,pthread 库确实在用户空间维护了一套数据结构来管理线程,而内核则继续使用 task_struct 来维护和管理进程与线程的执行。两者之间通过系统调用进行交互,用户空间的数据结构(如 pthread_t)在本质上是对内核线程的一个抽象。

  • 11
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值