Linux->线程库接口

2023-11-08

目录

前言:

1 进程和线程

2 线程库接口

2.1 线程库基础理解

2.2 创建线程

2.2 线程资源回收

 2.3 线程分离


前言:

        本篇主要是对Linux原装线程库的函数接口进行学习,还有一部分的线程概念补充。

1 进程和线程

        博主在上一篇文章当中有讲过,进程是承担资源分配的载体,而线程是CPU调度的基本单位,并且所有的线程能够共享进程的数据。这没有什么问题,但是这难道不会有一点奇怪吗?

        那就是如果所有的线程都能够共享地址空间的数据,那么这是不是就意味着多个线程同时进入一个函数,它们所看到的东西都是一样的?如果是一样的,这样有问题吗?如果不是一样的,OS又是如何识别是哪一个线程正在执行这个函数呢?

        这个时候就需要引入一个线程私有属性的概念了:

线程 ID
一组寄存器
独立栈
errno
信号屏蔽字
调度优先级

        上面的这几个属性当中,最重要的就是独立栈还有寄存器了,另外需要了解的就是线程ID,其余的几个知道就行了。

        为什么我说独立栈和寄存器是最重要的呢?因为明白线程有的这两个属性,才能真正明白线程切换还有线程运行时的行为特征,对于这部分博主会在讲解接口的时候为大家展开讲解的。

        线程之间的共享属性

文件描述符表
每种信号的处理方式 (SIG_ IGN SIG_ DFL 或者自定义的信号处理函数 )
当前工作目录
用户 id 和组 id

        其实上面的共享属性博主虽然说是线程之间的,但是本质上其实是进程级别的属性,只是线程本身没有额外的添加,从而直接把进程当中的拿过来了。为什么不添加呢?原因很简单,这些属性有且只能有一份存在,多了会导致程序执行紊乱。我用信号处理方式为例,如果线程1对于野指针问题导致的段错误的处理方式是忽略操作,而线程2对于野指针异常信号是直接让程序退出,那么真正来了一个段错误信号之后,进程应该采用哪一种方式呢?所以这部分属性是必须保持一致的。

2 线程库接口

2.1 线程库基础理解

        首先,博主在上一篇文章当中讲过,在Linux当中是没有真正的线程的,只有轻量级进程的概念,所以在Linux当中只有轻量级进程的接口,但是对于我们用户来说,我们是不认识什么轻量级进程的,我们只认线程,只要线程接口,但是Linux当中又没有,怎么办?只能是额外设计一个线程库用来封装轻量级进程的接口。因为有了这样一个库的存在,用户还是使用的线程的方法,而OS本质上还是使用的自己的那一套轻量级进程的管理方案。

        如下图就是Linux为我们添加的动态线程库:

        看到这个动态库,大家有没有想到一个东西呢?那就是在使用编译指令的时候需要在后面添加上一个-lpthread这样的一个后缀,否则OS就像是眼睛瞎了一样找不到,这一部分博主在动静态库连接的过程当中有讲过,博主也就不再赘述了。如下:

mythread:mythread.cc
	g++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:
	rm -f mythread

​

2.2 创建线程

        创建线程的函数接口如上图,这个函数的第一个参数是输出型参数,用于创建线程之后为我们返回一个线程ID这个东西,第二个表示线程的私有属性结构,一般来说我们不用对它做修改,所以传参时,直接传入一个nullptr就行,第三个参数表示我们要给线程执行的任务,因为它本身就是一个函数指针,之后传入一个返回类型为void*,参数也为void*的函数指针即可。第四个参数是一个输入型参数,这个参数的类型为void*,这表示了之后执行传入的函数的参数就是它。如下为使用方式。

代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;

void toHex(int tid)
{
    printf("%x\n",tid);
}

void *running1(void *args)
{
    // 对无类型的数据做类型转换
    string data = static_cast<const char *>(args);

    while (true)
    {
        // 输出“hello world”表示正确
        cout << data << "线程ID ";
        toHex(pthread_self());
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid1;
    const char *data = "hello world";

    // 创建线程
    int n = pthread_create(&tid1, nullptr, running1, (void *)data);

    // 创建线程返回值不为0表示错误
    if (n != 0)
    {
        cout << "create thread fail" << endl;
    }

    while (1)
    {
        cout << "I am main thread" << " ";
        toHex(pthread_self());
        sleep(1);
    }

    return 0;
}

 输出:

         如上代码可以看到,我们生成了线程,因为分别执行了两个死循环,所以证明了我们的线程已经生成了成功,并且,通过我们的线程执行函数里面,收到了主线程传递的参数,也可以证明我们的参数传递也是正确的。

        当然我这里只是简单的传入了一个字符串,其实最重要的是,这个参数使用无类型接收的参数,所以它可以传入任意的一个值都是没有问题的,什么意思呢?也就是说,我们的这个参数可以传递任意的类,结构体,这才是真正有意义的。

线程ID和LWP:

        大家看到了上面的输出结果,为什么会有线程ID和LWP两种不同的东西来表示我们的线程啊?这样做感觉好奇怪,有什么实际的意义吗?

        其实这样做确实是有它自己的意义的,首先,LWP所对应的值是什么?轻量级进程,这个是用来给操作系统看的,但是线程ID呢?这一点我们就不能简单的理解它了,我问一个问题,我们的线程被创建出来之后,他需要管理吗?需要被组织吗?当然需要,但是这是由谁来做的呢?难道是操作系统吗?

        事实上操作系统确实是做了一部分的管理和组织的功能,但是还有一部分内容是OS所不方便代为执行的,我们的线程栈还有线程局部存储,原因如下:

  1. 方便快捷:线程 ID 是一个整数类型的变量,易于在程序中进行传递和管理。

  2. 动态性:线程 ID 在创建线程时动态分配,并且可以重复使用,因此能够很好地适应不同场景下的线程管理需求。

  3. 安全性:使用线程 ID 能够保证每个线程的资源可以被独立地管理,从而避免多个线程之间产生冲突和竞争等问题。

        为什么这么说呢?还记得我们的线程库是怎么来的吗?通过封装LWP接口来的,所以按照实现的角度来说,是可以通过LWP来实现的,但是我们不这么做就是为了保证线程栈的独立性。

        看到我们输出图的线程ID转为16进制之后的值了吗?它是什么?它是一个地址,表示线程栈的首地址,因为这个地址是不相同的,所以每一次CPU执行有相同函数的线程的时候是不会出现紊乱的操作的,这样讲大伙估计有一些疑问,那么请看下图:

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;

void toHex(int tid)
{
    printf("%x\n",tid);
}

void *running1(void *args)
{
    // 对无类型的数据做类型转换
    //string data = static_cast<const char *>(args);
    int num = 100;
    while (true)
    {
        // 输出“hello world”表示正确
        //cout << data << "线程ID ";
        //oHex(pthread_self());

        cout << num << " " << &num << endl;
        sleep(1);
    }

    return nullptr;
}

int main()
{
    pthread_t tid1,tid2;
    //const char *data = "hello world";

    // 创建线程
    int n = pthread_create(&tid1, nullptr, running1, nullptr);
    int y = pthread_create(&tid2, nullptr, running1, nullptr);
    // 创建线程返回值不为0表示错误
    if (n != 0 || y != 0)
    {
        cout << "create thread fail" << endl;
    }


    while (1)
    {
        // cout << "I am main thread" << " ";
        // toHex(pthread_self());
        sleep(1);
    }

    return 0;
}

         我们看到上面的代码,如果说不同的线程进入了相同的函数之后,看到的是同一份资源,那么这个资源的地址按照道理来说应该是相同的,但是事实是这样吗?并不是,也不应该是一样的,否则我们的程序不就直接乱套了嘛。

       其实我们可以想象到操作系统是如何做的,我们的地址计算方式其实很简单,线程不是带有寄存器的数据嘛,那么一定有对应的寄存器是用来表示我们的线程独立栈的起始位置,和结束位置,然后通过起始地址偏移,就能找到所有变量在不同线程当中地址位置了。

2.2 线程资源回收

        大家有没有注意到,前面博主故意在回避线程执行完毕的返回值?并且我们的主线程创建完成线程之后还需要加一个死循环防止线程退出,这样的方式确实是有一点点挫了,所以这个时候就有了另外一个线程接口了:

         这个函数接口表示在主线程只有等待线程执行完毕之后才能继续相同运行,第一个参数就是我们的线程ID,用来识别是哪一个线程退出了,第二个参数就涉及到了我们的线程执行完毕之后的返回值了,因为线程的返回值是一个void*类型,如果我们直接传递void*类型去接收,那么必然会出现形参接收值得情况,这是我们不希望看到的,所以只能通过二级指针的方式接收。

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <string>
using namespace std;

void toHex(int tid)
{
    printf("%x\n",tid);
}

typedef struct point
{
    int x;
    int y;
}point;

void *running1(void *args)
{
    int n = 10;
    while (n--)
    {
        cout << n << endl;
        sleep(1);
    }
    point* ret = new point;
    ret->x = 2;
    ret->y = 1;

    return (void*)ret;
}

int main()
{
    pthread_t tid1;
    // 创建线程
    int n = pthread_create(&tid1, nullptr, running1, nullptr);
    // 创建线程返回值不为0表示错误
    if (n != 0)
    {
        cout << "create thread fail" << endl;
    }

    point* ret = nullptr;

    pthread_join(tid1, (void**)&ret);
    
    cout << ret->x << ' ' << ret->y << endl;

    return 0;
}

         如上图可以看出,返回值可以是一个结构体,返回之后可以在主线程当中被使用,有意思波。并且大家有没有想过不使用return返回,而是通过exit退出会怎么样呢?很简单,整个进程直接退出了,这个是进程退出的函数啊伙伴们,虽然我们不能使用exit但是我们可以使用另外一个函数:

         这个函数的使用方式和return相同,博主也不想多讲,相信大家也是能明白的。

 2.3 线程分离

        基于上面的线程退出资源等待,我们有一个问题,那就是我的主线程并不关心你的退出资源,我主线程也有自己的事情要做,但是你却强制要我等你,这不是很烦的一件事情嘛,所以基于这个需求,线程还有一个接口用来分离线程:

         使用了这个函数之后,线程会与主线程分离,并且在此之后,主线程甚至不能去等待它了,等待就会出错,就像是我们分手了之后还是别去找对方比较好,省的还被骂一顿。

pthread_detach(tid1);

pthread_join(tid1, (void**)&ret);

        如上代码,我在join之前最线程进行分离,运行会出现什么问题?

         看吧,会出现问题的,等待函数直接不起作用了,所以记住线程分离之后是不能在等待的。

        直接退出的原因是由于等待函数不起作用了,并且主线程并没有需要执行的内容,进程直接退出,资源被释放了。


        以上就是博主对这一部分知识的群不理解了,希望能够帮助到大家。 这里博主抛出一个问题,那就是多线程同时对一个全局变量进行修改是否会出现什么问题呢?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux->线程库接口 的相关文章

  • 除了 iptables 之外还有数据包管理实用程序吗? [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 我正在寻找一个 Linux 实用程序 它可以根据一组规则更改网络数据包的有效负载 理想情况下 我会使用
  • 从 Java 读取 /dev/input/js0

    我正在尝试阅读 dev input js0来自Java 但我不断得到 java io IOException Invalid argument at java io FileInputStream read0 Native Method a
  • 不同GIT版本的GIT合并结果不同

    在不同的 GIT 版本上运行 merge 命令我们得到不同的结果 命令是 git merge no ff origin master codeline Results 版本2 1 4 gt 合并成功 版本1 7 1 gt 同一提交上的同一合
  • libusb 和轮询/选择

    我正在使用 Linux 操作系统 想知道是否有任何文件描述符可以轮询 选择 当数据等待从 USB 设备读取时会触发这些文件描述符 我也在使用 libusb 库 但尚未找到可以使用的文件描述符 Use libusb 的轮询函数 http li
  • 干净地销毁System V共享内存段

    我在用shmget shmat and shmctl分别获取和创建共享内存段 将其附加到进程地址空间中并删除它 我想知道进程是否仍然可以使用共享内存段 即使它已被分离并要求使用删除 shmctl id IPC RMID 在一个过程中 我无法
  • 从 gitlab docker runner 启动声纳扫描仪

    我有一个 CI 工作流程 集成了 linting 作业和代码质量作业 我的 Linting 工作是一个 docker runner 从应用程序代码启动我的 eslint 脚本 然后我的代码质量工作应该启动声纳扫描仪泊坞窗实例 检查我的代码并
  • 代码::块 - 警告:GDB:无法设置控制终端:不允许操作

    我已经通过官方存储库在 Ubuntu 14 04 中安装了 Code Blocks 13 12 当我编译时 一切正常 但是当我调试时 shell 中会显示以下消息 警告 GDB 无法设置控制终端 操作不正确 允许的 程序执行到断点 但当我执
  • 如何修改s_client的代码?

    我正在玩apps s client c in the openssl源代码 我想进行一些更改并运行它 但是在保存文件并执行操作后 我的更改没有得到反映make all or a make 例如 我改变了sc usage函数为此 BIO pr
  • 当存在点和下划线时,使用 sed 搜索并替换

    我该如何更换foo with foo sed 只需运行 sed s foo foo g file php 不起作用 逃离 sed s foo foo g file php Example cat test txt foo bar sed s
  • 是否有我可以运行的操作系统命令来确定是否在基于 Xen 的虚拟机内运行

    我可以在基于 Xen 的虚拟机中运行一个操作系统命令来告诉我它是一个虚拟机而不是物理机 我听说内核对此有一些自我意识智能 例如就像 ps 输出中的额外列之类的 我知道 vmstat 提供了 st 列 但我在运行 Linux 内核 2 6 1
  • Linux下的C#,Process.Start()异常“没有这样的文件或目录”

    我在使用 Process 类调用程序来启动程序时遇到问题 可执行文件的层次结构位于 bin 目录下 而当前工作目录需要位于 lib 目录下 project bin a out this is what I need to call lib
  • 有关 Linux 内存类型的问题

    关于Linux内存我有以下问题 我知道活动内存是最常访问的内存部分 但是有人可以解释一下 linux 如何考虑将内存位置用于活动内存或非活动内存 主动存储器由哪些部分组成 磁盘 文件缓存是否被视为活动内存的一部分 有什么区别Buffers
  • 是否可以找到哪个用户位于 localhost TCP 连接的另一端?

    这是一个编程问题 但它是 Linux Unix 特定的 如果我从本地主机获得 TCP 连接 是否有一种简单的方法可以告诉哪个用户在 C 程序内建立了连接而无需 shell 我知道这对于 Unix 域套接字来说并不太难 我已经知道远程 IP
  • 如何使用libaudit?

    我试图了解如何使用 libaudit 我想接收有关使用 C C 的用户操作的事件 我不明白如何设置规则 以及如何获取有关用户操作的信息 例如 我想获取用户创建目录时的信息 int audit fd audit open struct aud
  • 使用 MongoDB docker 镜像停止虚拟机而不丢失数据

    我已经在 AWS EC2 上的虚拟机中安装了官方的 MongoDB docker 映像 并且数据库上已经有数据 如果我停止虚拟机 以节省过夜费用 我会丢失数据库中包含的所有数据吗 在这些情况下我怎样才能让它持久 有多种选择可以实现此目的 但
  • 测试linux下磁盘空间不足

    我有一个程序 当写入某个文件的磁盘空间不足时 该程序可能会死掉 我不确定是否是这种情况 我想运行它并查看 但我的测试服务器不会很快耗尽空间 有什么办法可以嘲笑这种行为吗 看起来没有任何方法可以在 Ubuntu 中设置文件夹 文件大小限制 并
  • 使用netcat将unix套接字传输到tcp套接字

    我正在尝试使用以下命令将 unix 套接字公开为 tcp 套接字 nc lkv 44444 nc Uv var run docker sock 当我尝试访问时localhost 44444 containers json从浏览器中 它不会加
  • ARM 系统调用的接口是什么?它在 Linux 内核中的何处定义?

    我读过有关 Linux 中的系统调用的内容 并且到处都给出了有关 x86 架构的描述 0x80中断和SYSENTER 但我无法追踪 ARM 架构中系统调用的文件和进程 任何人都可以帮忙吗 我知道的几个相关文件是 arch arm kerne
  • 通过 SSH 将变量传递给远程脚本

    我正在通过 SSH 从本地服务器在远程服务器上运行脚本 首先使用 SCP 复制该脚本 然后在传递一些参数时调用该脚本 如下所示 scp path to script server example org another path ssh s
  • 如何在 Linux 中使用单行命令获取 Java 版本

    我想通过单个命令获取 Linux 中的 Java 版本 我是 awk 的新手 所以我正在尝试类似的事情 java version awk print 3 但这不会返回版本 我将如何获取1 6 0 21从下面的Java版本输出 java ve

随机推荐

  • 基于人工蜂群算法的函数寻优算法

    文章目录 一 理论基础 二 算法流程 1 初始化阶段 2 引领蜂阶段 3 跟随蜂阶段 4 侦察蜂阶段 5 食物源 三 MATLAB程序实现 1 清空环境变量 2 问题设定 3 参数设置 4 初始化蜜蜂种群 5 迭代优化 6 结果显示 四 参
  • php 发送邮箱 Email

    步骤一 phpmailer 很好 无论原生还是放到框架下 phpmailer下载地址 https github com PHPMailer PHPMailer
  • 华中农业大学数学实验期末考试答案(matlab)

    1 这题通过生成一个全是1的矩阵 然后加上一个对角阵就行了 A ones 10 10 3 diag 0 9 DET A det A INV A inv A 2 这一题之前我写过一个求线性方程组的小程序 求解线性方程组 3 function
  • 【mmdetection】使用自定义的coco格式数据集进行训练及测试

    目录 一 mmdetection简介 二 环境安装 1 安装教程 2 运行demo测试环境是否安装成功 三 训练自定义的dataset 1 准备dataset 2 Training前修改相关文件 3 Training 四 Testing 五
  • 使用FastApi服务解决程序反复调试导致速度过慢的问题(以tsfresh为例)

    对于多次调试的程序来说 重复执行如 读取数据 加载模型 得到导入的外部数据或三方库等重复操作的过程 可以使用网络服务搭建一个类似API一样的操作 一次读取 终身使用 整体的思路如下 本地搭建一个web服务 如本博客使用FastAPI 也可换
  • Java Session 会话技术

    一 Session简介 Session技术是将数据存储在服务器端的技术 会每个客户端都创建一块内存空间存储客户的数据 但客户端需要都携带一个标识ID去服务器中寻找属于自己的内存空间 所以说Session的实现是基于Cookie Sessio
  • 超参数优化--随机网格法

    随机网格搜索RandomizedSearchCV 在网格搜索时我们提到 伴随着数据和模型的复杂度提升 网格搜索所需要的时间急剧增加 以随机森林算法为例 如果使用过万的数据 搜索时间则会立刻上升好几个小时 因此 我们急需寻找到一种更加高效的超
  • vue 数组添加数据

    vue 数据添加分为三种方法 1 unshift 2 push 3 splice
  • vue点击导航 页面自动滚动到特定位置

    vue点击导航 页面自动滚动到特定位置 效果预览 1 npm i element ui S 下载安装element组件库 导航我们使用element组件库中的样式 type primary 刚好作为我们导航激活后的样式 省去了我们写样式的时
  • AVR 中 delay 函数的调用注意事项!delay_ns delay_ms

    早就知道AVR的编译器有自带的延时子函数 或者说是头文件 但一直没时间一探究竟 今天终于揭开了其内幕 AVR编译器众多 可谓是百家齐鸣 本人独尊WinAVR 说明 编译器版本WinAVR 20080610 先说winAVR的 Delay h
  • java 远程连接_java连接远程服务器(示例代码)

    我用的是smb协议 共享方式连接远程 Windows服务器 也可以用ftp 但要保证服务器是ftp的 连接Linux服务器可以用ssh 协议 新建一个res properites连接 IP 10 61 28 56 SMB MINGCHENG
  • 第7章 指针 第1题

    题目 用原型 void getDate int dd int mm int yy 写一个函数 从键盘读入一个形如dd mmm yy的日期 其中dd是一个1位或2位的表示日的整数 mmm是月份的3个字母的缩写 yy是两位数的年份 函数读入这个
  • teamviewer连接不上的原因及解决方法有哪些

    teamviewer连接不上的原因及解决方法有哪些 一 总结 一句话总结 这里说的就是版本问题 高版本可以连接低版本 低版本无法连接高版本 1 TeamViewer官方检测使用环境是否为商用的标准是什么 1 自安装软件以来 累计连接的电脑多
  • 这个人就是吴恩达(Andrew Ng),百度新任首席科学家

    这个人就是吴恩达 Andrew Ng 百度新任首席科学家 虎嗅 2013 05 11 10 32 收藏43 评论35 虎嗅注 人工智能现在是科技界最前沿的话题之一 以谷歌为代表 科技巨头均在这个方向上进行巨大投入 虎嗅曾发表过一篇文章 谷歌
  • 【神兵利器】介绍一款基于GPT-4完全免费的编程软件:Cursor!

    Cursor 一款基于GPT 4完全免费的编程软件 PS 文章首发于公众号 字节卷动 官网地址 官网 https www cursor so IDE作者 https twitter com amanrsanger 这是我找到的第一个免费的
  • python比较两个csv文件,并打印出不同的行号,列号,数据

    https blog csdn net The Handsome Sir article details 121251433 def compareFile file1 file2 如果相等返回 1 0 0 如果不相等返回 0 a b a
  • 【满分】【华为OD机试真题2023 JS】AI处理器组合

    华为OD机试真题 2023年度机试题库全覆盖 刷题指南点这里 AI处理器组合 知识点数组 时间限制 1s 空间限制 256MB 限定语言 不限 题目描述 某公司研发了一款高性能AI处理器 每台物理设备具备8颗AI处理器 编号分别为0 1 2
  • Y形电路与三角电路转换,网孔和节点分析法

    Y形电路与三角电路转换 网孔和节点分析法 Y形电路与三角电路转换 推导过程与之前的电压源和电流源的转换类似 用系数相等即可等价转换 此处直接给出结论与记法 网孔分析法 自电阻 在这个网孔中所有电阻的和 互电阻 网孔1与网孔2之间的电阻 将每
  • 机器学习——决策树算法

    一 实验目的 掌握如何实现决策树算法 用并决策树算法完成预测 二 实验内容 本次实验任务我们使用贷款申请样本数据表 该数据表中每列数据分别代表ID 年龄 高薪 有房 信贷情况 类别 我们根据如下数据生成决策树 使用代码来实现该决策树算法 三
  • Linux->线程库接口

    目录 前言 1 进程和线程 2 线程库接口 2 1 线程库基础理解 2 2 创建线程 2 2 线程资源回收 2 3 线程分离 前言 本篇主要是对Linux原装线程库的函数接口进行学习 还有一部分的线程概念补充 1 进程和线程 博主在上一篇文