Linux(线程库和线程封装)
线程库和线程封装
- 1 理解线程库
- 1.1 线程库被加载到内存
- 1.2 线程ID
- 1.3 线程栈
- 2 线程封装(用C++封装线程)
1 理解线程库
1.1 线程库被加载到内存
(1)上面的这份代码我们上一节已经编写过了,结果也在图中展示出来了,我们可以发现所谓的tid其实就是一个地址
(2)前面我们学过线程的各种函数是依赖于库的,库也要被加载到内存中,它也会通过页表被映射到进程地址空间中的共享区,因为线程共享进程地址空间中的各种区,所以共享区也被共享,所以所有线程都能够看到线程库,所以新线程和主线程都能使用各种线程函数
1.2 线程ID
(1)Linux中根本就没有线程这个概念,只有LWP,但是我们用户要用线程,所以OS是封装了线程接口,但是如果我们想要线程属性呢(id,优先级,状态,栈大小、、、、)?从OS的角度出发,是不是应该将线程的属性集维护起来,在创建线程的同时就将其属性填充好,用户只需要获得线程的属性,而不是获得LWP的属性
(2)另一个进程里的线程属性需不需要被维护?所以库为了管理所有的线程属性集,需要先描述再组织(tcb),所以OS维护的是PCB,库维护的是tcb
(3)一个线程库内部维护了所有线程的属性集合,所以线程ID就是线程属性在线程库中的地址
(3)下面这张图就是共享区内的线程库示意图,其中我们可以将线程库理解为数组存储tcb,我们只需要找到每一个线程tcb的开始地址就可以获取到整个tcb的内容
(4)这个struct pthrea里面一定封装了lwb
(5)线程之间上下文独立,但栈只有一个,所以我们每一个线程的栈都是被独立创建出来的,结论就是进程地址空间上的栈是主线程的栈,而新线程的栈都是被动态申请的
(6)所谓的线程局部存储就是通过关键字 --thread ,让变量在每一个线程中存在一份(只能修饰内置类型)
上图中第一行的value只存在一份,也就是每一个线程拿到的变量地址都是一样的,其中一个线程中的该值发生改变,所有线程中的都发生变化,而下面一行中的value每个线程拿到的地址都不一样,并且值不会随一个线程改变而改变(errno就是基于这种方式实现的)
1.3 线程栈
总之线程栈默认大小为8M,其不能动态增长
2 线程封装(用C++封装线程)
(1)封装的目的:1、为了让我们更熟练使用线程函数的接口 2、实现多线程更方便
(2)demo样例:
makefile:
Thread.hpp:(不能传参数 )
#include <iostream>
#include <string>
#include <functional>
#include <sys/types.h>
#include <unistd.h>namespace THreadModule
{using func_t = std::function<void()>;static int number = 1;enum class TSTATUS // 枚举线程的状态,因为线程结束,线程类不一定结束{NEW,RUNNING,STOP};class Thread{private:static void *Routine(void *args) // 成员方法!第一个参数是this指针{//_func();// 之所以创建时最后一个传this,是因为Routine无法使用_func函数,// 因为它需要this才行,但是Routinue被我们转化为了static函数,没有this指针了Thread *t = static_cast<Thread *>(args);t->_func();t->_status = TSTATUS::RUNNING;return nullptr;}void EnableDetach() // 是否可分离{_joinable = false;}public:Thread(func_t func) : _func(func), _status(TSTATUS::NEW), _joinable(true){_name = "Thread-" + std::to_string(number++); // 给线程起名字_pid = getpid();}bool Start() // 线程启动{if (_status == TSTATUS::NEW){int n = ::pthread_create(&_tid, nullptr, Routine, this); // 为什么用this?if (n != 0)return false;}return true;}bool Stop() // 线程终止{if (_status == TSTATUS::RUNNING){int n = pthread_cancel(_tid);if (n != 0)return false;_status = TSTATUS::STOP;}return true;}bool Join() // 等待线程结束{if (_joinable){int n = ::pthread_join(_tid, nullptr);if (n != 0)return false;_status = TSTATUS::STOP;}return true;}void Detach() // 线程分离{EnableDetach();pthread_detach(_tid);}bool IsJoinable() // 是否可等待{return _joinable;}std::string Name(){return _name;}~Thread(){}private:std::string _name; // 线程名字pthread_t _tid; // 线程idpid_t _pid; // 线程属于哪个进程bool _joinable; // 是否是分离的,默认不是func_t _func; // 线程执行什么函数TSTATUS _status; // 线程状态};
}
测试的main.cc
多线程的main.cc:
传参数的thread.hpp:(看懂即可,我们正常用不到参数),我只给出了不同的部分,其他部分完全一致
namespace THreadModule
{static int number = 1;enum class TSTATUS // 枚举线程的状态,因为线程结束,线程类不一定结束{NEW,RUNNING,STOP};template <typename T>class Thread{using func_t = std::function<void(T)>;private:static void *Routine(void *args) // 成员方法!第一个参数是this指针{//_func();// 之所以创建时最后一个传this,是因为Routine无法使用_func函数,// 因为它需要this才行,但是Routinue被我们转化为了static函数,没有this指针了Thread<T> *t = static_cast<Thread<T> *>(args);t->_func(t->_data);t->_status = TSTATUS::RUNNING;return nullptr;}public:Thread(func_t func, T data) : _func(func), _data(data), _status(TSTATUS::NEW), _joinable(true){_name = "Thread-" + std::to_string(number++); // 给线程起名字_pid = getpid();}private:std::string _name; // 线程名字pthread_t _tid; // 线程idpid_t _pid; // 线程属于哪个进程bool _joinable; // 是否是分离的,默认不是func_t _func; // 线程执行什么函数TSTATUS _status; // 线程状态T _data;};
}
参数的传递: