✨个人主页: 熬夜学编程的小林
💗系列专栏: 【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; // 线程tid
bool _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指针 + static
int 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;
}
运行结果