thread id 本质是一个地址。
以十六机制打印id。
线程终止的两种方法。
1.直接return;
2.pthread_exit();
注意exit()是用来终止进程的,不能用于线程。
那怎么获取线程的返回值呢?
首先,和进程一样,线程退出也需要等待,不然也会导致僵尸问题。
int pthread_join(pthread_t thread, void **retval);
args:
pthread_t thread: 被连接线程的线程号
void **retval : 指向一个指向被连接线程的返回码的指针的指针
return:
线程连接的状态,0是成功,非0是失败
#include<iostream>
#include<string>
#include<functional>
#include<vector>
#include<pthread.h>
#include<unistd.h>
#include<time.h>
using namespace std;
using func_t=function<void()>;
const int threadnum=5;
class ThreadData
{
public:
ThreadData(const string&name,const uint64_t&ctime,func_t f)
:threadname(name)
,createtime(ctime)
,func(f)
{}
public:
string threadname;
uint64_t createtime;
func_t func;
};
string to_Hex(pthread_t tid)
{
char id[64];
snprintf(id,sizeof id,"0x%x",tid);
return id;
}
void *ThreadRoutine(void*args)
{
int a=2;
ThreadData*td=static_cast<ThreadData*>(args);
while(a--)
{
cout<<"I am new Thread "<<td->threadname<<' '<<td->createtime<<' '<<to_Hex(pthread_self())<<' '<<endl;
td->func();
// if(td->threadname=="thread-4")
// {
// a/=0;
// }
sleep(1);
}
pthread_exit((void*)"thread 1");
}
void print()
{
cout<<"我是线程执行的大任务的一部分"<<endl;
}
int main()
{
pthread_t tid;
char threadname[64];
snprintf(threadname,sizeof threadname,"%s-%d","thread",1);
ThreadData*td=new ThreadData(threadname,uint64_t(time(nullptr)),print);
pthread_create(&tid,nullptr,ThreadRoutine,td);
cout<<to_Hex(tid)<<endl;
void *ret=nullptr;
int n=pthread_join(tid,&ret);
cout<<"main thread done"<<" n: "<<n<<' '<<(const char*)ret<<endl;
return 0;
}
我们要要得到为void*的返回值,那么就需要传入void**,所以是&ret。
线程的等待不需要像进程一样获取异常,因为一出问题就挂。
#include<iostream>
#include<string>
#include<functional>
#include<vector>
#include<pthread.h>
#include<unistd.h>
#include<time.h>
using namespace std;
using func_t=function<void()>;
const int threadnum=5;
class ThreadData
{
public:
ThreadData(const string&name,const uint64_t&ctime,func_t f)
:threadname(name)
,createtime(ctime)
,func(f)
{}
public:
string threadname;
uint64_t createtime;
func_t func;
};
class ThreadReturn
{
public:
ThreadReturn(pthread_t id,int code,string info)
:_id(id)
,_code(code)
,_info(info)
{}
public:
pthread_t _id;
int _code;
string _info;
};
string to_Hex(pthread_t tid)
{
char id[64];
snprintf(id,sizeof id,"0x%x",tid);
return id;
}
void *ThreadRoutine(void*args)
{
int a=2;
ThreadData*td=static_cast<ThreadData*>(args);
while(a--)
{
cout<<"I am new Thread "<<td->threadname<<' '<<td->createtime<<' '<<to_Hex(pthread_self())<<' '<<endl;
td->func();
// if(td->threadname=="thread-4")
// {
// a/=0;
// }
sleep(1);
}
ThreadReturn* ret=new ThreadReturn(pthread_self(),10,"quit normal");
//pthread_exit((void*)ret);
return ret;
}
void print()
{
cout<<"我是线程执行的大任务的一部分"<<endl;
}
int main()
{
pthread_t tid;
char threadname[64];
snprintf(threadname,sizeof threadname,"%s-%d","thread",1);
ThreadData*td=new ThreadData(threadname,uint64_t(time(nullptr)),print);
pthread_create(&tid,nullptr,ThreadRoutine,td);
cout<<to_Hex(tid)<<endl;
void *ret=nullptr;
int n=pthread_join(tid,&ret);
ThreadReturn*r=static_cast<ThreadReturn*>(ret);
//cout<<"main thread done"<<" n: "<<n<<' '<<(ThreadReturn*)ret<<endl;
cout<<r->_id<<' '<<r->_code<<' '<<r->_info<<endl;
return 0;
}
线程的返回值,可以是结构化的数据。
线程的等待默认是joinable的,如果阻塞等待失败可能会有僵尸问题。
我们可以把它设置成分离状态。
实际上,像是QQ这样的软件,死循环运行才是常态。
为了让线程分离,我们可以调用pthread_detach函数。
- 函数描述:实现线程分离
- 函数原型:int pthread_detach(pthread_t thread);
- 函数返回值:成功:0;失败:错误号
也可用pthread_cancel(pthread_t tid)直接取消进程。
pthread原生线程库中会有一个存储tcb(线程控制模块)的数组,用户和系统之间,线程与lwp一一对应。
tcb组成:
1、threadID:线程的唯一标识。
2、status:线程的运行状态
3、register:线程关于CPU中寄存器的情况
4、PC程序计数器:线程执行的下一条指令的地址
5、优先级:线程在操作系统调度的时候的优先级
6、线程的专属存储区:线程单独的存储区域
7、用户栈:线程执行的用户方法栈,用来保存线程当前执行的用户方法的信息
8、内核栈:线程执行的内核方法栈,用来保存线程当前执行的内核方法信息
线程的上下文是由操作系统以轻量级进程的方式保存在pcb之中的。
栈的寄存器只有一套,怎么兼顾多个线程呢?
创建轻量级进程用到的函数,是pthread_create()底部封装的函数。
第一个参数是回调方法, 第二个参数是用户传入的栈,允许用户指定栈。
每个新线程的栈,在库中维护。
C语言缓冲区,每一个C语言文件对象中会存在一个缓冲区,所以我们说缓冲区是C语言维护的,
说白了,就是在C库中维护的。
所以,一个库可以对一块内存空间进行维护。
同样的,pthread库也是库,在库中实现pthread_create的时候,内部包的就是clone函数。
在调它之前,先malloc申请空间,然后把空间传给stack,充当新线程的栈。
库一般被加载到堆栈之间的共享区。
pthread一旦被加载到内存中,要经过页表映射,最终映射到当前进程的共享区。
那么线程的栈在哪里呢?
pthread内部会帮我们new出来一段空间,把堆空间的地址写到pthread库里面,在库里用指针维护,线程退出时把空间释放掉就行。
默认地址空间中的栈由主线程使用,这样,可以保证每一个新线程都可以保存要执行的方法,要传递的参数以及动态运行时的临时变量。
和可执行程序一样,pthread库会通过页表从内存映射到地址空间堆栈之间的共享区。
创建线程的时候,在正文代码区,直接跳转到共享区的库里,执行创建线程的函数,然后返回。
而线程库是共享的,所以,内部要管理整个系统,多个用户启动的所有的线程。
如图,共享区里有动态库和有线程结构体组成的数组。
调用线程时,将结构体中的数据传入clone函数。
也可调用pthread_join从中获取返回值。
tid代表线程属性集合在库中的地址。