【Linux系统编程】第四十四弹---从TID到线程封装:全面掌握线程管理的核心技巧
✨个人主页: 熬夜学编程的小林
💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】
目录
1、tid是什么
1.1、理解库
1.2、理解tid
1.3、tid中线程局部存储
2、封装线程
2.1、基本结构
2.2、函数实现
2.3、使用单线程
2.4、使用多线程
1、tid是什么
从前面的讲解我们猜测tid是一个虚拟地址,并使用代码打印出来,此处需要更进一步分析tid,先代码演示一下!!!
用到的头文件
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <string>
#include <cstdio>
代码演示
std::string ToHex(pthread_t tid)
{char id[128];snprintf(id,sizeof(id),"0x%lx",tid);return id;
}void* threadrun(void* args)
{std::string name = static_cast<const char*>(args);while(true){// std::cout << name << " is running...,tid: " << pthread_self() << std::endl; std::cout << name << " is running...,tid: " << ToHex(pthread_self()) << std::endl; sleep(1);}
}int main()
{pthread_t tid;pthread_create(&tid,nullptr,threadrun,(void*)"thread-1");// std::cout << "new thread tid: " << tid << std::endl;std::cout << "new thread tid: " << ToHex(tid) << std::endl;pthread_join(tid,nullptr);return 0;
}
运行结果
1.1、理解库
讲解tid之前我们需要先理解一下库
1、库是一个文件,保存在磁盘中,然后将库加载到内存,再通过页表将库映射到地址空间中
2、创建线程,前提是把库加载到内存,映射到进程的内存空间
3、创建线程,调用线程库中的系统调用函数
1.2、理解tid
库是如何做到对线程进行管理的呢?
先描述(库中创建描述线程的相关结构体字段属性),在组织("数组")。
如何理解这个虚拟地址?
可以类比C语言打开文件,通过地址找到文件!!!
tid是什么?
线程属性集合的起始虚拟地址--- 在pthread库中维护
1.3、tid中线程局部存储
编写C++代码
int gval = 100;std::string ToHex(pthread_t tid)
{char id[128];snprintf(id, sizeof(id), "0x%lx", tid);return id;
}void *threadrun(void *args)
{std::string name = static_cast<const char *>(args);while (true){std::string id = ToHex(pthread_self());std::cout << name << " is running...,tid: " << id << ",gval: " << gval << ",&gval: " << &gval << std::endl;sleep(1);gval++;}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, threadrun, (void *)"thread-1");while (true){std::cout << "main thread,gval: " << gval << ",&gval: " << &gval << std::endl;sleep(1);}pthread_join(tid, nullptr);return 0;
}
运行结果
如果想要每个线程使用独立的内存空间呢?
加__thread (两个下划线)修饰全局变量,即在线程局部存储中开辟空间!!!
- 定义:__thread是GCC内置的线程局部存储设施,用于创建线程局部变量。
- 作用:保证每个线程拥有独立的变量副本,各个线程对该变量的操作互不干扰。
// Linux有效, __thread只能修饰内置类型
__thread int gval = 100;
运行结果
2、封装线程
2.1、基本结构
线程的封装可以使用命名空间域,类成员变量有name,tid,isrunning,func!!!
namespace ThreadMoudle
{// 线程要执行的方法typedef void (*func_t)(const std::string& name); // 函数指针类型class Thread{public:// 执行回调函数void Excute();public:Thread(const std::string& name,func_t func):_name(name),_func(func);// 新线程执行该方法static void* ThreadRoutine(void* args);// 线程状态std::string Status();// 启动线程bool Start();// 停止线程void Stop();// 线程名字std::string Name();// 等待线程void Join();~Thread();private:std::string _name; // 线程名pthread_t _tid; // 线程tidbool _isrunning; // 线程状态func_t _func; // 线程要执行的回调函数};
}
2.2、函数实现
构造函数
打印一条语句即可!
Thread(const std::string& name,func_t func):_name(name),_func(func)
{std::cout << "create " << _name << " done" << std::endl;
}
启动线程
启动线程的本质是创建一个新线程,创建成功返回true,创建失败返回false。
1、创建新线程需要调用执行函数,但是成员函数会有隐含的this指针,直接调用成员函数会报错,因为执行函数只能有一个参数,解决办法是使用静态成员函数,该函数属于类,不属于对象,因此没有隐含的this指针。
2、使用静态成员函数之后,没有this指针,就不能直接调用类对象的方法,此处的解决办法是将this指针传给执行函数。
void Excute()
{std::cout << _name << " is running" << std::endl;_isrunning = true;_func(_name);_isrunning = false;
}
// 新线程执行该方法
static void* ThreadRoutine(void* args)
{// _func(); // static 不能直接访问成员函数,没有this,传this指针Thread* self = static_cast<Thread*>(args);self->Excute();return nullptr;
}
bool Start()
{// ::使用库函数接口,直接使用ThreadRoutine会报错,因为成员函数有隐含this指针 + staticint n = ::pthread_create(&_tid,nullptr,ThreadRoutine,this);if(n != 0) return false;return true;
}
停止线程
如果线程处于运行状态则需要停止线程,停止的本质是取消线程,修改线程运行状态即可!
void Stop()
{if(_isrunning){::pthread_cancel(_tid);_isrunning = false;std::cout << _name << " Stop" << std::endl;}
}
等待线程
等待线程即调用等待线程的系统调用即可!
void Join()
{::pthread_join(_tid,nullptr);std::cout << _name << " Join" << std::endl;
}
其他函数
std::string Status()
{if(_isrunning)return "running";else return "sleep";
}
std::string Name()
{return _name;
}
~Thread()
{}
2.3、使用单线程
自己管理单线程!! 先描述在组织!!!
void Print(const std::string &name)
{int cnt = 1;while (true){std::cout << name << " is running,cnt: " << cnt++ << std::endl;sleep(1);}
}int main()
{Thread t("thread-1", Print);t.Start();sleep(1); // 等待一秒,因为线程执行顺序不确定,可能主线程后执行std::cout << t.Name() << ",status: " << t.Status() << std::endl;sleep(10);t.Stop();sleep(1);std::cout << t.Name() << ",status: " << t.Status() << std::endl;t.Join();std::cout << t.Name() << ",status: " << t.Status() << std::endl;return 0;
}
2.4、使用多线程
要使用多线程,实质就是创建多个线程,然后将多线程管理起来,我们可以使用vector容器!
新线程执行函数
void Print(const std::string &name)
{int cnt = 1;while (true){std::cout << name << " is running,cnt: " << cnt++ << std::endl;sleep(1);}
}
主函数
const int gnum = 10;int main()
{std::vector<Thread> threads;// 创建线程for(int i=0;i<gnum;i++){std::string name = "thread" + std::to_string(i+1);threads.emplace_back(name,Print);sleep(1);}// 统一启动for(auto& thread : threads){thread.Start();}sleep(3);// 统一停止for(auto& thread : threads){thread.Stop();}// 统一等待for(auto& thread : threads){thread.Join();}return 0;
}
运行结果