当前位置: 首页 > news >正文

Linux《线程(下)》

通过之前进程(上)的学习我们已经了解了线程基本的概念以及在Linux当中线程是如何实现的,还了解了pthread库当中线程相关的接口,那么接下来在本篇当中将继续进行线程相关的学习,在此会继续学习线程相关的接口,并且还要了解线程在内存当中具体的空间布局是什么样的,最后将试着使用pthread库当中提供的线程的接口实现一个类似C++11线程当中的线程库。接下来就开始本篇的学习吧!!!



1.线程接口补充

在上一篇章当中我们已经学习了pthread_create线程超级、pthread_join线程等待的相关的接口,那么接下来还需要学习以下的接口。

1.1 线程终止

在一个线程当中要退出可以有以下的三种方式可以实现:
1.在线程执行所在的函数但这个调用return,但是该方法在main函数所在的主线程当中不适用,因为在主线程当中使用return相当与调用exit。

2.在线程执行所在的函数当中调用pthread_exit

3.在主线程当中调用pthread_cancel函数来终止指定的线程

pthread_exit函数如下所示:

#include <pthread.h>void pthread_exit(void *value_ptr);参数:
value_ptr:value_ptr不要指向⼀个局部变量。返回值:
⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调用者(自身)

例如以下代码:

#include<iostream>
#include<pthread.h>
#include<unistd.h>void* routine(void* args)
{int cnt=5;while(cnt--){const char* s=static_cast<const char*>(args);std::cout<<"我是线程:"<<s<<" 正在运行"<<std::endl;sleep(1);}return nullptr;}int main()
{pthread_t t1;pthread_create(&t1,nullptr,routine,(void*)"pthread-1");while(1){std::cout<<"我是主线程,真正运行当中……"<<std::endl;sleep(1);};pthread_join(t1,nullptr);return 0;
}

通过输出的结果机就可以看出我们创建出来的线程在5s之后就退出了

pthread_cancel函数

#include<pthread.h>
功能:取消⼀个执⾏中的线程
int pthread_cancel(pthread_t thread);参数:
thread:线程ID返回值:成功返回0;失败返回错误码

例如以下示例:

#include<iostream>
#include<pthread.h>
#include<unistd.h>void* routine(void* args)
{while(1){const char* s=static_cast<const char*>(args);std::cout<<"我是线程:"<<s<<" 正在运行"<<std::endl;sleep(1);}return nullptr;}int main()
{pthread_t t1;pthread_create(&t1,nullptr,routine,(void*)"pthread-1");int cnt=0;while(1){std::cout<<"我是主线程,真正运行当中……"<<std::endl;sleep(1);++cnt;if(cnt==3)pthread_cancel(t1);};pthread_join(t1,nullptr);return 0;
}

以上的代码当中在主线程当中在3s之后就将创建出来的线程1给终止了,运行程序输出的结果如下所示:

1.2 分离线程

通过之前的学习我们知道在线程创建出来之后都是需要主线程进行线程等待的,否则就会出现内存泄漏的问题,但是实际上如果主线程对其创建出来的线程的退出码不关心,那么这时候子再进行线程的等待就对主线程是一种负担,因此在pthread库当中提供的对应进行线程分离的函数pthreqad_detach,那么调用该函数之后线程在退出之后就会自动释放自身的资源。

#include<pthread.h>int pthread_detach(pthread_t thread);参数:
thread:要分离的线程 ID。返回值:
0 → 成功
非 0 → 错误码(如 ESRCH:线程不存在,EINVAL:线程已经是分离状态)。

例如以下代码:

#include <iostream>
#include <pthread.h>
#include <unistd.h>void *routine(void *args)
{const char *s = static_cast<const char *>(args);std::cout << "我是线程:" << s << " 正在运行" << std::endl;sleep(1);return nullptr;
}int main()
{pthread_t t1;pthread_create(&t1, nullptr, routine, (void *)"pthread-1");pthread_detach(t1);sleep(1);std::cout << "主线程执行结束" << std::endl;sleep(2);return 0;
}

2. 线程ID及进程地址空间布局

以上我们了解了线程以及pthread库当中线程相关的接口之后,接下来就来来了解线程给用户提供的线程ID的本质以及在进程当中线程地址空间的布局具体是什么样的。

通过以上的学习我们了解到了在使用pthread_create的时候首先需要创建一个pthread_t的对象,那么问题就来了在此的pthread_t类型究竟是什么呢?

实际上线程 ID 类型是 pthread_t,在pthreat_t 并不是内核中提出的,而是在 NPTL(Native POSIX Thread Library,Linux 的 POSIX 线程实现)里,本质上就是一个和 LWP(轻量级进程,Linux 下的内核线程)对应的标识。

实际上pthread_t是结构体struct pthread 的引用(或索引/指针/数值 ID,具体实现依赖 glibc)。

那么这时在用户的视角当中pthread_t就是线程的ID,而就不再需要再去了解不同系统当中线程具体的实现是什么样的,例如在Linux当中是底层是LWP等。

在此struct pthread 的结构体当中的数据如下所示:

struct pthread {/* ---- 基本线程管理 ---- */struct pthread *self;        // 指向自己,快速获取 TCBpid_t tid;                   // 线程在内核中的 TIDpid_t pid;                   // 所属进程的 PID/* ---- 线程栈相关 ---- */void *stack_base;            // 栈的基地址size_t stack_size;           // 栈的大小void *stack_guard;           // 栈保护区(防止栈溢出)/* ---- 线程状态 ---- */int detach_state;            // 线程是 joinable 还是 detachedint canceled;                // 是否被取消int exit_state;              // 退出状态void *retval;                // 线程返回值(用于 pthread_join)/* ---- 同步与等待 ---- */struct pthread *joinid;      // 谁在等待我(join 的线程)pthread_cond_t join_cond;    // join 等待的条件变量pthread_mutex_t lock;        // 用于保护 TCB 的锁/* ---- 线程局部存储 TLS ---- */void **tls;                  // 线程局部存储指针size_t tls_size;             // TLS 的大小/* ---- 信号相关 ---- */sigset_t sigmask;            // 线程的信号屏蔽字void *signal_stack;          // 信号处理栈/* ---- 调度与优先级 ---- */int sched_policy;            // 调度策略(SCHED_FIFO, SCHED_RR, SCHED_OTHER)int sched_priority;          // 调度优先级
};

在进程的虚拟地址空间当中就会存在对应的mmap当中存储不同线程对应的结构体,在结构体当中就会存储线程的信息。除此之外每个线程还会有自己独立的线程局部存储,该部分的作用就是在线程退出的时候将退出的信息存储,之后主线程就可以通过该空间得到,并且每个线程还有自己的线程栈;线程在运行的时候就会将局部变量等存储在该区域,实际上就可以理解为线程就是基于该空间运行的。

当多个线程同时运行的时候场景就如下所示,本质上就是和之前我们学习多个进程同时加载同一个动态库类似。

在pthread库当中提供了得到当前线程ID的函数pthread_self

#include <pthread.h>pthread_t pthread_self(void);

3.线程封装

以上我们了解了线程概念以及pthread线程库当中线程的接口之后那么接下来就试着来使用以上的函数来实现出面向对象式的线程类

实现的代码如下所示:

#pragma once#include <iostream>
#include <functional>
#include <pthread.h>
#include <string.h>
#include <unistd.h>namespace ThreadModlue
{//线程ID,从1开始static int number = 1;//线程类class Thread{//线程运行执行的函数using func_t = std::function<void()>;//将线程分离void EnableDetach(){std::cout << "线程被分离" << std::endl;_isdetach = true;}//将线程的状态置为运行void EnableRunning(){_isrunning = true;}//线程运行函数,线程执行函数时参数只能包含一个args参数,因此需要将Routine函数置为静态成员函数static void *Routine(void *args){Thread *self = static_cast<Thread *>(args);self->EnableRunning();if (self->_isdetach)self->Detach();pthread_setname_np(self->_tid, self->_name.c_str());self->_func();return nullptr;}public://构造函数Thread(func_t func): _tid(0),_isdetach(false),_isrunning(false),res(nullptr),_func(func){_name = "thread-" + std::to_string(number++);}//线程分离void Detach(){if (_isdetach)return;if (_isrunning)pthread_detach(_tid);EnableDetach();}//启动线程bool Start(){if (_isrunning)return false;int n = pthread_create(&_tid, nullptr, Routine, this);if (n != 0){std::cerr << "create thread error!" << strerror(n) << std::endl;return false;}else{std::cout << "ctrate success!" << std::endl;return true;}}//终止线程bool Stop(){if (_isrunning){int n = pthread_cancel(_tid);if (n != 0){std::cerr << "cancel pthread error!" << strerror(n) << std::endl;return false;}else{_isrunning = false;std::cout << "cancel pthread success!" << std::endl;return true;}}return false;}//线程等待bool Join(){if (_isdetach){std::cout << "你的线程已经是分离的了,不能再进行join" << std::endl;return false;}int n = pthread_join(_tid, &res);if (n != 0){std::cerr << "join pthread error!" << strerror(n) << std::endl;return false;}else{std::cout << "join pthread success!" << std::endl;return true;}}~Thread(){}private://线程IDpthread_t _tid;//线程名std::string _name;//线程是否分离标志位bool _isdetach;//线程是否运行标志位bool _isrunning;void *res;//线程运行执行函数func_t _func;};
}

以上就是本篇的所有内容的,在此我们就将线程基本的概念了解完毕,接下来的篇章当中将开始线程控制相关的学习……


文章转载自:

http://DTArrzs5.dwwLg.cn
http://Io3IEa2q.dwwLg.cn
http://gQZchYy8.dwwLg.cn
http://rv5A9nvn.dwwLg.cn
http://cYSau5Yt.dwwLg.cn
http://IbgcoGY3.dwwLg.cn
http://3KMLfw7w.dwwLg.cn
http://O9NO6OJW.dwwLg.cn
http://mbjQB36V.dwwLg.cn
http://ky88PEKo.dwwLg.cn
http://Zwcos5LU.dwwLg.cn
http://ltXEMxHh.dwwLg.cn
http://14DJ1Jep.dwwLg.cn
http://aeeGly9m.dwwLg.cn
http://VOVPnQzb.dwwLg.cn
http://Z7uOVY4C.dwwLg.cn
http://CsDHlQpJ.dwwLg.cn
http://rDXrQX06.dwwLg.cn
http://rzgIqFrJ.dwwLg.cn
http://piPtTTVo.dwwLg.cn
http://YSLh4mPm.dwwLg.cn
http://n0gOm4PX.dwwLg.cn
http://Ow3P0IjD.dwwLg.cn
http://rfpE6IKe.dwwLg.cn
http://9laBoJbf.dwwLg.cn
http://A72wtJ1G.dwwLg.cn
http://TbGSQzpi.dwwLg.cn
http://URBYxhNz.dwwLg.cn
http://uQpFCKfL.dwwLg.cn
http://cSsweU0w.dwwLg.cn
http://www.dtcms.com/a/387369.html

相关文章:

  • 第二部分:VTK核心类详解(第20章 vtkCamera相机类)
  • 线性回归与 Softmax 回归:深度学习入门核心模型解析
  • K8s配置管理:ConfigMap与Secret核心区别
  • 【Qt开发】显示类控件(四)-> QCalendarWidget
  • 【K8S系列】Kubernetes 调度与资源管理深度剖析:Requests、Limits、QoS 与 OOM
  • 小程序地图以及讲解的使用
  • 单分类线性逻辑回归
  • 使用POSTMAN 创建泛微OA流程
  • vscode中配置pytest
  • 液氮低温恒温器的应用领域
  • [Yolo遇到的问题] 使用VScode进行ultralytics训练 启动后在scanning阶段意外中断 导致训练无法正常启动
  • 微算法科技(NASDAQ:MLGO)研究分布式量子计算,释放量子计算潜能
  • 使用EasyExcel读不到数据的低级问题
  • 万象EXCEL开发(一)表头标尺搭建—东方仙盟筑基期
  • Redis 发展趋势与 Redis 7.x 新特性:从缓存到实时数据平台的演进
  • 微信小程序 tabBar 切换实现
  • 微信小程序的跳转方式
  • 微信小程序---暮之沧蓝音乐小程序
  • springboot jar包部署到服务器上后,logback按日期归档不正确,今天的日志归档到昨天了,日志中的时间也不正确
  • Spring Boot Logback 日志配置详解:从基础到分布式追踪
  • 辉视养老方案:重塑老年生活的温馨与安心
  • 通过商业智能(BI)可视化数据分析了解布洛芬的产销情况
  • 健康大数据专业能转行做医疗数据分析吗?
  • antiword为什么在ubuntu22.04上面不乱码,而在mac上出现乱码
  • Paperless-ngx v2.18.4在Ubuntu 24.04上的完整离线安装步骤(非Docker)
  • Ubuntu 18.04 搭建 Kubernetes 1.27.4 集群全流程(附问题排查)
  • Ubuntu 18.04 LTS 安装 6.10.10 内核
  • Windows 11 下使用 WSL2 安装 Ubuntu 22.04 步骤
  • 在 WSL 中通过 Bash 函数快速转换 Windows 路径为 Ansible/WSL 路径
  • 【ubuntu24.04】 nvidia-smi监控GPU 利用率