[linux仓库]线程库封装[线程·肆]
🌟 各位看官好,我是egoist2023!
🌍 Linux == Linux is not Unix !
🚀 学习了线程的概念及操作之后,模仿C++对线程操作的封装自己造一个线程封装的轮子。
👍 如果觉得这篇文章有帮助,欢迎您一键三连,分享更多人哦!
目录
回顾与总结
线程封装
线程启动
线程等待
总结
附源码
回顾与总结
进程是承担分配资源的基本实体,本质:
- 进程=内核数据结构 +代码和数据
 - 内核数据结构:不管是PCB,文件地址空间,文件描述符表,信号相关的话题,通信模块 ->都是数据结构对象 ->要占内存
 - 代码和数据:本质是ELF加载到内存,本身也是要占据内存.
 - 无论是进程是新建时还是运行后,无时无刻在占据资源 ->不管是写实拷贝还是缺页中断,都是要占内存的.
 - 一个进程在运行期间,也是要占据CPU调度资源,切换成本.在I0领域要占用带宽
 
线程是调度的基本单位:
- 如果申请的资源能划分成若干份,每一个执行流拥有一个资源,就叫做线程
 
站在一款具体OS视角来理解:
- 进程角度:进程 =内核数据结构(...)+ 代码和数据
 - 线程角度:进程内部运行,创建PCB,用进程控制块来模拟线程,提高可维护性
 
一个线程?会把资源做划分?
- 一个进程看待资源,是通过地址空间+页表方式,即拥有虚拟地址的个数,占据的资源会更多.
 - 编译器是进行资源划分的兑现者,0S是进行申请资源的执行者
 - 线程角度:每一个线程,函数本身是代码块的集合,都要有地址,是代码块的入口地址,线程以某一个函数为入口,ELF被编址后,该线程就拥有整个虚拟地址的一部分。
 - 编译器角度:把虚拟地址空间给线程划分好
 
本质:划分虚拟地址资源!
一个线程出现异常,整个进程就会崩溃?
- 线程是代表一个进程;
 - 线程出现异常,0S就会给进程发送信号,就会被杀掉,而进程的资源就会被释放,而线程用的资源是进程提供的,没有生存空间
 
页表和页表项:
逻辑上是个表结构,实际上 无符号长整数
typedef struct { unsigned long pte; } pte_t; // 页表项
typedef struct { unsigned long pgd; } pgd_t; // 页全局目录项 
那页表是什么呢?
页表项1024所对应的数组都是1024项,都用的是2^10次方

把页表的起始地址拷贝到pgd里面,没有任何权限:
- pgd_t[1024]就是页目录表,就是个整形数组
 - pte_t[1024]也是页表啊,也是个整形数组
 
但是,我们页表不是有用户级页表和内核级页表吗?不是有对应的读写权限吗?那么哪有位置给它们写入呢?只有32bit位
 物理内存的每一个页框的低12位都是全0,那么就只需要用pte整数前20位就可以表示页框地址,低12位就可以充当标志位了,表示页框的状态.(我们的page也是有这几个状态的啊,直接查page不就可以了?低12位是为了增加效率)
1024个整形数组,不就是4kb吗?每一个表都占据一个页框
线程封装
我们将仿照C++封装Linux中的线程的做法,做一个独属于自己的线程库,并且这个线程库之后也需要被我们自己扩展使用.
封装的线程库的成员变量如下:
pthread_t _tid;
pid_t _lwpid;
std::string _name;
func_t _fun;
bool _isrunning;
 
线程启动
    void *start_route(void *args){std::string name = static_cast<char*>(args);}int start(){int n = pthread_create(&_tid, nullptr, start_route, (void*)_name.c_str());if (n == 0){std::cout << "run thread success" << std::endl;}} 
pthread_create创建线程时,需要交代一个新线程要执行的函数,函数内部会进行回调func方法.而上述代码为什么会报红呢?本质上是成员函数含有隐藏的this指针,只允许一个参数.那么该如何解决这个问题呢?
- 放在类外
 - 用static进行修饰
 
如果使用static修饰,那么就只能访问静态成员变量了,无法直接访问实例的成员,此时就不能访问func包装器了.那么又该怎么做呢?
为了解决静态函数无法访问实例成员的问题,可以将当前对象的 this 指针作为参数传递给回调函数。通过将 this 指针传递给静态回调函数,start_route 可以通过 this 指针访问实例的成员变量和方法。
    using func_t = std::function<void()>;static void *start_route(void *args){Thread *self = static_cast<Thread *>(args);self->_isrunning = true;self->_lwpid = get_lwp_id();self->_fun();pthread_exit((void *)0);}int start(){int n = pthread_create(&_tid, nullptr, start_route, this);if (n == 0){std::cout << "run thread success" << std::endl;}} 
线程等待
    void join(){if (!_isrunning)return;int n = pthread_join(_tid, nullptr);if (n == 0){std::cout << "pthread_join success" << std::endl;}} 
如果要像C++11那样进行可变参数的传递,是可以这样设计的,但是太⿇烦了,真到了哪⼀步,就直接⽤c++11吧,我们的⽬标主要是理解系统概念对象化,此处不做复杂设计,⽽且后续可以使⽤std::bind来进行对象间调用
总结
本文探讨了Linux系统中进程与线程的核心概念及其实现原理。首先分析了进程本质是"内核数据结构+代码和数据"的组合体,详细解释了进程资源分配机制。然后从操作系统角度比较了进程和线程的区别,指出线程作为调度基本单位通过划分虚拟地址资源来优化性能。文章还深入解析了页表结构及权限管理机制,特别是12位标志位的设计原理。最后通过C++代码示例展示了线程封装技术,包括回调函数设计、静态成员访问解决方案,并提供了完整的线程库实现代码。全文涵盖了从理论到实践的线程管理知识,为深入理解Linux系统编程提供了实用指导。
附源码
Thread.hpp
#ifndef __THREAD_HPP__
#define __THREAD_HPP__#include <vector>
#include <pthread.h>
#include <string>
#include <functional>
#include <iostream>
#include <unistd.h>#define get_lwp_id() syscall(SYS_gettid)using func_t = std::function<void()>;
const std::string threadnamedefault = "None-Name";class Thread
{
public:Thread(func_t func, const std::string &name = threadnamedefault): _fun(func), _name(name){std::cout << "创建了一个线程" << std::endl;}static void *start_route(void *args){Thread *self = static_cast<Thread *>(args);self->_isrunning = true;self->_lwpid = get_lwp_id();self->_fun();pthread_exit((void *)0);}int start(){int n = pthread_create(&_tid, nullptr, start_route, this);if (n == 0){std::cout << "run thread success" << std::endl;}}void join(){if (!_isrunning)return;int n = pthread_join(_tid, nullptr);if (n == 0){std::cout << "pthread_join success" << std::endl;}}~Thread(){}private:pthread_t _tid;pid_t _lwpid;std::string _name;func_t _fun;bool _isrunning;
};#endif 
testThread.cc
#include "Thread.hpp"void test()
{int cnt = 5;while (cnt--){std::cout << "新线程运行了" << std::endl;sleep(1);}
}int main()
{std::vector<Thread> tids;for (int i = 0; i < 5; i++){std::string name = "thread-";name += std::to_string(i + 1);Thread t(test, name);tids.push_back(t);}for (auto &tid : tids){tid.start();}// 回收线程for (auto &tid : tids){tid.join();}return 0;
} 


