线程
- 在一个程序里的一个执行路线就叫做线程(thread)。更准确的定义是:线程是“一个进程内部的控制序列”。
- 一切进程至少都有一个执行线程。
- 线程在进程内部运行,本质是在进程地址空间内运行。
- 在Linux系统中,在CPU的视角中,看到的PCB都要比传统的进程更轻量化。
- 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流
进程地址空间是进程的资源窗口,线程分配资源,本质就是分配地址空间范围
进程的创建,伴随着其进程控制块(task_struct)、进程地址空间(mm_struct)以及页表的创建,虚拟地址和物理地址就是通过页表建立映射的
如果在创建“进程”时,只创建task_struct,并要求创建出来的task_struct和父task_struct共享进程地址空间和页表,那么创建的结果就是下面这样的:
此时我们创建的实际上就是四个线程:
其中每一个线程都是当前进程里面的一个执行流,每一个线程是进程内部的一个执行分支
同时我们也可以看出,线程本质是在进程地址空间内运行,也就是说这个进程申请的所有资源,几乎都是被所有线程共享的。
在内核角度来理解进程:进程是承担分配系统资源的基本实体,线程是操作系统调度的基本单位
当我们创建进程时是创建task_struct、进程地址空间、页表,然后在物理内存当中开辟空间、构建页表映射,打开进程默认打开的相关文件、注册信号对应的处理方案等等
只有一个task_struct,也就是该进程内部只有一个执行流,即单执行流进程
内部有多个执行流的进程叫做多执行流进程。
- 在Linux中,站在CPU的视角,能否识别当前调度的task_struct是进程还是线程?
不能,CPU只关心一个一个的独立执行流。无论进程内部是只有一个执行流还是有多个执行流,CPU都是以task_struct为单位进行调度的
- Linux下并不存在真正的多线程!而是用进程模拟的
Linux没有重新为线程设计数据结构,而是直接复用了进程控制块,所以我们说Linux中的所有执行流都叫做轻量级进程。
既然在Linux没有真正意义的线程,那么也就没有真正意义上的线程相关的系统调用
但是Linux可以提供创建轻量级进程的接口,也就是创建进程,共享空间,其中最典型的代表就是vfork函数。
页表
在Linux中,32位平台下用的是二级页表,而64位平台下用的是多级页表
以32位平台为例,其页表的映射过程如下:
- 选择虚拟地址的前10个比特位在页目录当中进行查找,找到对应的页表。
- 再选择虚拟地址的10个比特位在对应的页表当中进行查找,找到物理内存中对应页框的起始地址。
- 最后将虚拟地址中剩下的12个比特位作为偏移量从对应页框的起始地址处向后进行偏移,找到物理内存中某一个对应的字节数据。
关于物理内存:
物理内存被划分成一个个4KB大小的页框,磁盘上的程序也是被划分成一个个4KB大小的页帧,当内存和磁盘进行数据交换时,是以4KB大小为单位进行加载和保存的
4KB实际上就是212个字节,也就是说一个页框中有212个字节,而访问内存的基本大小是1字节,因此一个页框中就有212个地址,于是我们就可以将剩下的12个比特位作为偏移量,从页框的起始地址处开始向后进行偏移,从而找到物理内存中某一个对应字节数据
其中页目录项是一级页表,页表项是二级页表
每一个表项还是按10字节计算,页目录和页表的表项都是210个,因此一个表的大小就是210 *10个字节,也就是10KB。而页目录有210个表项也就意味着页表有210个,也就是说一级页表有1张,二级页表有210张,总共算下来大概就是10MB,内存消耗并不高,因此Linux中实际就是这样映射的。
上面所说的所有映射过程,都是由MMU(MemoryManagementUnit)这个硬件完成的,该硬件是集成在CPU内的。页表是一种软件映射,MMU是一种硬件映射,所以计算机进行虚拟地址到物理地址的转化采用的是软硬件结合的方式。
线程比进程要更轻量化
原因:1、创建和释放更加轻量化 ,创建线程只需要创建tcb , 创建进程需要创建task_struct、进程地址空间、页表,然后在物理内存当中开辟空间、构建页表映射,打开进程默认打开的相关文件、注册信号对应的处理方案等等
2 、切换更加轻量化,线程切换,需要线程的上下文恢复, 页表、地址空间不需要切换,只需要局部的上下文 ,并且不需要重新cache数据
POSIX线程库
pthread线程库是应用层的原生线程库:
- 应用层指的是这个线程库并不是系统接口直接提供的,而是由第三方帮我们提供的。
- 原生指的是大部分Linux系统都会默认带上该线程库。
- 与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”打头的。
- 要使用这些函数库,要通过引入头文件<pthreaad.h>。
- 链接这些线程函数库时,要使用编译器命令的“-lpthread”选项
pthread_create
pthread_create,创建线程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
thread:获取创建成功的线程ID,该参数是一个输出型参数。
attr:用于设置创建线程的属性,传入NULL表示使用默认属性。
start_routine:该参数是一个函数地址,表示线程例程,即线程启动后要执行的函数。
arg:传给线程例程的参数
线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象
class Request
{
public:
Request(int start, int end, const string &threadname)
: start_(start)
, end_(end)
, threadname_(threadname)
{}
public:
int start_;
int end_;
string threadname_;
};
class Response
{
public:
Response(int result, int exitcode)
:result_(result)
,exitcode_(exitcode)
{}
public:
int result_; // 计算结果
int exitcode_; // 计算结果是否可靠
};
void * sumCount(void * args)//线程的参数和返回值,不仅仅可以用来进行传递一般参数,也可以传递对象!!
{
Request * rq = ( Request *)args;
Response * rsp =new Response (0,0);
for( int i =rq->start_ ; i<=rq->end_ ; i++)
{
cout << rq->threadname_ << " is runing, caling..., " << i << endl;
rsp->result_+=i;
usleep(100000);
}
delete rq ;
return rsp; //隐式类型转换
}
int main()
{
pthread_t tid ;
Request *rq = new Request(1,100,"thread 1");
//创建线程
pthread_create(&tid, nullptr , sumCount, rq) ;
//线程等待
void * ret ;
pthread_join(tid, &ret); //ret 拿到新创建的线程的返回值
Response * rsp = (Response *)ret ;
cout << "rsp->result: " << rsp->result_ << ", exitcode: " << rsp->exitcode_ << endl;
delete rsp;
return 0 ;
}
return val : 线程创建成功返回0,失败返回错误码
当一个程序启动时,就有一个进程被操作系统创建,与此同时一个线程也立刻运行,这个线程就叫做主线程。
- 主线程是产生其他子线程的线程。
- 通常主线程必须最后完成某些执行操作,比如各种关闭动作
示例:
#include<iostream>
#include <pthread.h>
#include<string>
#include<unistd.h>
using namespace std ;
void * threadRoutine(void * args)
{
while(true)
{
cout << "new thread, pid: " << getpid() << endl;
sleep(1);
}
}
int main()
{
pthread_t tid ;
//创建线程
pthread_create(&tid ,nullptr , threadRoutine,nullptr); // threadRoutine 是函数指针
while(true)
{
cout << "main thread, pid: " << getpid() << endl;
sleep(1);
}
}
该进程中有两个线程,但是我们看到的进程只有一个,因为这两个线程都是属于同一个进程的。
ps -aL命令,可以显示当前的轻量级进程。
- 默认情况下,不带-L,看到的就是一个个的进程。
- 带-L就可以查看到每个进程内的多个轻量级进程
[cxq@iZwz9fjj2ssnshikw14avaZ lesson35]$ ps -aL
PID LWP TTY TIME CMD
15277 15277 pts/0 00:00:00 mythread
15277 15278 pts/0 00:00:00 mythread
15280 15280 pts/1 00:00:00 ps
LWP(Light Weight Process)就是轻量级进程的ID,可以看到显示的两个轻量级进程的PID是相同的,因为它们属于同一个进程。
线程等待
一个线程被创建出来,是需要被等待的。如果主线程不对新线程进行等待,那么这个新线程的资源也是不会被回收的。如果不等待会产生类似于“僵尸进程”的问题,也就是内存泄漏
pthread_join ,调用该函数的线程将挂起等待,直到ID为thread的线程终止,thread线程以不同的方法终止,通过pthread_join得到的终止状态是不同的。
- 如果thread线程通过return返回,retval所指向的单元里存放的是thread线程函数的返回值。
- 如果thread线程被别的线程调用pthread_cancel异常终止掉,retval所指向的单元里存放的是常数PTHREAD_CANCELED。
- 如果thread线程是自己调用pthread_exit终止的,retval所指向的单元存放的是传给pthread_exit的参数。
- 如果对thread线程的终止状态不感兴趣,可以传NULL给retval参数
pthread_join
int pthread_join(pthread_t thread, void **retval);
- thread:被等待线程的ID。
- retval:线程退出时的退出码信息
return val :线程等待成功返回0,失败返回错误码
#include<iostream>
#include <pthread.h>
#include<string>
#include<unistd.h>
using namespace std ;
void * threadRoutine(void * args)
{
//新创建的线程获取主线程传的参数"Thread 1"
const char * name = (const char *) args;
int cnt =5 ;
while(true)
{
printf("%s\n" , name);
sleep(1);
cnt --;
if(cnt ==0)
break;
}
//(void*)1 ,将1的地址强制转换为void *
return (void*)1 ; //代码运行到这里 ,新创建的线程就退出了
}
int main()
{
pthread_t tid ;
//创建线程
pthread_create(&tid ,nullptr , threadRoutine, (void *)"Thread 1"); // threadRoutine 是函数指针
//主线程等待,是阻塞等待的
sleep(7);
void * retval;
pthread_join(tid,&retval) ; //retval 拿到新创建的线程的返回值
cout << "main thread quit ..., ret: " <<(long long int)retval<< endl;
}
直接调用exit, exit是用来终止进程的,不能用来直接终止线程
#include<iostream>
#include <pthread.h>
#include<string>
#include<unistd.h>
#include<cstdlib>
using namespace std ;
void * threadRoutine(void * args)
{
//新创建的线程获取主线程传的参数"Thread 1"
const char * name = (const char *) args;
int cnt =5 ;
while(true)
{
// cout << "new thread, pid: " << getpid() << endl;
printf("%s\n" , name);
sleep(1);
cnt --;
if(cnt ==0)
break;
}
exit(11);// 直接调用exit, exit是用来终止进程的!!,不能用来直接终止线程
}
int main()
{
pthread_t tid ;
//创建线程
pthread_create(&tid ,nullptr , threadRoutine, (void *)"Thread 1"); // threadRoutine 是函数指针
void * retval;
pthread_join(tid,&retval) ; //retval 拿到新创建的线程的返回值
cout << "main thread quit ..., ret: " <<(long long int)retval<< endl;
}
线程终止
需要只终止某个线程而不是终止整个进程,可以有三种方法
1、从线程函数return。
在线程中使用return代表当前线程退出,但是在main函数中使用return代表整个进程退出,也就是说只要主线程退出了那么整个进程就退出了,此时该进程曾经申请的资源就会被释放,而其他线程会因为没有了资源,自然而然的也退出了
#include<iostream>
#include <pthread.h>
#include<string>
#include<unistd.h>
#include<cstdlib>
using namespace std ;
void * threadRoutine(void * args)
{
//新创建的线程获取主线程传的参数"Thread 1"
const char * name = (const char *) args;
int cnt =5 ;
while(true)
{
printf("%s\n" , name);
sleep(1);
cnt --;
if(cnt ==0)
break;
}
//(void*)1 ,将1的地址强制转换为void *
return (void*)1 ; //代码运行到这里 ,新创建的线程就退出了
}
int main()
{
pthread_t tid ;
//创建线程
pthread_create(&tid ,nullptr , threadRoutine, (void *)"Thread 1"); // threadRoutine 是函数指针
//主线程等待,是阻塞等待的
//sleep(7);
void * retval;
pthread_join(tid,&retval) ; //retval 拿到新创建的线程的返回值
cout << "main thread quit ..., ret: " <<(long long int)retval<< endl;
}
2、线程可以自己调用pthread_exit函数终止自己。
3、一个线程可以调用pthread_cancel函数终止同一进程中的另一个线程
pthread_exit,终止线程
void pthread_exit(void *retval);
- retval:线程退出时的退出码信息。
该函数无返回值,跟进程一样,线程结束的时候无法返回它的调用者(自身)。
pthread_exit或者return返回的指针所指向的内存单元必须是全局的或者是用malloc分配的,不能在线程函数的栈上分配,因为当其他线程得到这个返回指针时,线程函数已经退出了
示例:
void * threadRoutine(void * args)
{
//新创建的线程获取主线程传的参数"Thread 1"
const char * name = (const char *) args;
int cnt =5 ;
while(true)
{
printf("%s\n" , name);
sleep(1);
cnt --;
if(cnt ==0)
break;
}
//终止线程
pthread_exit( (void*) 200) ;
//(void*)1 ,将1的地址强制转换为void *
// return (void*)1 ; //代码运行到这里 ,新创建的线程就退出了
}
int main()
{
pthread_t tid ;
//创建线程
pthread_create(&tid ,nullptr , threadRoutine, (void *)"Thread 1"); // threadRoutine 是函数指针
//主线程等待,是阻塞等待的
//sleep(7);
void * retval;
pthread_join(tid,&retval) ; //retval 拿到新创建的线程的返回值
cout << "main thread quit ..., ret: " <<(long long int)retval<< endl;
}
监控脚本,可以查看线程的运行情况
while :; do ps -aL | head -1 && ps -aL | grep mythread ; sleep 1 ; done
pthread_cancel
pthread_cancel,一个线程可以调用pthread_cancel函数终止同一进程中的另一个线程
int pthread_cancel(pthread_t thread);
- thread:被取消线程的ID。
return val :线程取消成功返回0,失败返回错误码。
示例:
线程是可以取消自己的,取消成功的线程的退出码一般是-1
#include<iostream>
#include <pthread.h>
#include<string>
#include<unistd.h>
#include<cstdlib>
using namespace std ;
void * threadRoutine(void * args)
{
//新创建的线程获取主线程传的参数"Thread 1"
const char * name = (const char *) args;
int cnt =5 ;
while(true)
{
printf("%s\n" , name);
sleep(1);
cnt --;
if(cnt ==0)
break;
}
}
int main()
{
pthread_t tid ;
//创建线程
pthread_create(&tid ,nullptr , threadRoutine, (void *)"Thread 1"); // threadRoutine 是函数指针
sleep(1) ;//等待1s,给时间让新线程启动
pthread_cancel(tid); //主线程取消新创建的线程 ,主线程继续往后走 ,新线程已经退出
//主线程等待,是阻塞等待的
//sleep(7);
void * retval;
pthread_join(tid,&retval) ; //retval 拿到新创建的线程的返回值
cout << "main thread quit ..., ret: " <<(long long int)retval<< endl;
}
pthread_self
pthread_self, 获得当前线程的ID,类似于调用getpid函数获取当前进程的ID
pthread_t pthread_self(void);
创建三个线程,这三个线程都有各自独立的栈
示例:
#include<iostream>
#include<vector>
#include<unistd.h>
using namespace std ;
#define NUM 3
struct threadData
{
string tid ;
string threadname;
};
string toHex(pthread_t tid)
{
//将tid转为16进制
char buffer[128] ;
snprintf( buffer ,sizeof(buffer) , "0x%x", tid) ;
return buffer ;
}
void *threadRoutine(void *args)
{
threadData * td = (threadData * )args;
int i = 0;
int test_i = 0;
while (i <NUM)
{
cout << "pid: " << getpid() << " tid : "
<< toHex(pthread_self() ) << ", threadname: " << td->threadname
<<", test_i :"<<test_i
<<", &test_i :" <<toHex( (pthread_t) &test_i)<<endl;
sleep(1) ;
i++,test_i ++ ;
}
delete td;
return nullptr ;
}
void InitThreadData ( threadData * td , int number )
{
td->threadname = "thread-" + to_string( number) ;
}
int main()
{
//创建一批线程
vector<pthread_t > tids ;
for(int i =0 ; i < NUM ;i++)
{
// sleep(1) ;
pthread_t tid ;
//线程是共享堆的
threadData * td =new threadData ;
InitThreadData(td, i);
pthread_create(&tid , nullptr , threadRoutine,td );
tids.push_back(tid) ;
}
//将这一批线程等待
for( int i =0 ; i <tids.size() ; i++)
{
pthread_join(tids[i] , nullptr) ;
}
return 0 ;
}
每个线程创建的局部变量test_i依次从0开始增加到2,每个线程的t创建的test_i的地址也都不一样
主线程访问其他新线程的变量
#include<iostream>
#include<vector>
#include<unistd.h>
using namespace std ;
#define NUM 3
int * p = NULL ;
struct threadData
{
string tid ;
string threadname;
};
string toHex(pthread_t tid)
{
//将tid转为16进制
char buffer[128] ;
snprintf( buffer ,sizeof(buffer) , "0x%x", tid) ;
return buffer ;
}
void *threadRoutine(void *args)
{
int i = 0;
int test_i = 0;
threadData * td = (threadData * )args;
//把线程2的test_i变量的地址给指针p
if(td->threadname == "thread-2")
{
p = &test_i;
}
while (i <NUM)
{
cout << "pid: " << getpid() << " tid : "
<< toHex(pthread_self() ) << ", threadname: " << td->threadname
<<", test_i :"<<test_i
<<", &test_i :" << &test_i<<endl;
sleep(1) ;
i++,test_i ++ ;
}
delete td;
return nullptr ;
}
void InitThreadData ( threadData * td , int number )
{
td->threadname = "thread-" + to_string( number) ;
}
int main()
{
//创建一批线程
vector<pthread_t > tids ;
for(int i =0 ; i < NUM ;i++)
{
// sleep(1) ;
pthread_t tid ;
//线程是共享堆的
threadData * td =new threadData ;
InitThreadData(td, i);
pthread_create(&tid , nullptr , threadRoutine,td );
tids.push_back(tid) ;
}
sleep(1) ;
cout << "main thread get a thread local value, val: " << *p << ", &val: " << p << endl;
//将这一批线程等待
for( int i =0 ; i <tids.size() ; i++)
{
pthread_join(tids[i] , nullptr) ;
}
return 0 ;
}
线程的栈上的数据,也是可以被其他线程看到并访问的。
全局变量是被所有的线程同时看到并访问的
#include<iostream>
#include<vector>
#include<unistd.h>
using namespace std ;
#define NUM 3
int g_val= 100;
struct threadData
{
string tid ;
string threadname;
};
string toHex(pthread_t tid)
{
//将tid转为16进制
char buffer[128] ;
snprintf( buffer ,sizeof(buffer) , "0x%x", tid) ;
return buffer ;
}
void *threadRoutine(void *args)
{
int i = 0;
threadData * td = (threadData * )args;
while (i <NUM)
{
cout << "pid: " << getpid() << ", tid : "<<toHex(pthread_self() )
<< ", threadname: " << td->threadname
<< ", g_val: " << g_val << " ,&g_val: " << &g_val <<endl;
sleep(1) ;
i++,g_val++;
}
delete td;
return nullptr ;
}
void InitThreadData ( threadData * td , int number )
{
td->threadname = "thread-" + to_string( number) ;
}
int main()
{
//创建一批线程
vector<pthread_t > tids ;
for(int i =0 ; i < NUM ;i++)
{
// sleep(1) ;
pthread_t tid ;
//线程是共享堆的
threadData * td =new threadData ;
InitThreadData(td, i);
pthread_create(&tid , nullptr , threadRoutine,td );
tids.push_back(tid) ;
}
sleep(1) ;
//将这一批线程等待
for( int i =0 ; i <tids.size() ; i++)
{
pthread_join(tids[i] , nullptr) ;
}
return 0 ;
}
线程的局部存储!只能定义内置类型,不用用来修饰自定义类型
//线程局部存储
__thread int g_val= 100; // __是编译选项 ,将g_val这个变量设为线程的私有的全局变量
struct threadData
{
string tid ;
string threadname;
};
string toHex(pthread_t tid)
{
//将tid转为16进制
char buffer[128] ;
snprintf( buffer ,sizeof(buffer) , "0x%x", tid) ;
return buffer ;
}
void *threadRoutine(void *args)
{
int i = 0;
threadData * td = (threadData * )args;
while (i <NUM)
{
cout << "pid: " << getpid() << ", tid : "<<toHex(pthread_self() )
<< ", threadname: " << td->threadname
<< ", g_val: " << g_val << " ,&g_val: " << &g_val <<endl;
sleep(1) ;
i++,g_val++;
}
delete td;
return nullptr ;
}
void InitThreadData ( threadData * td , int number )
{
td->threadname = "thread-" + to_string( number) ;
}
int main()
{
//创建一批线程
vector<pthread_t > tids ;
for(int i =0 ; i < NUM ;i++)
{
// sleep(1) ;
pthread_t tid ;
//线程是共享堆的
threadData * td =new threadData ;
InitThreadData(td, i);
pthread_create(&tid , nullptr , threadRoutine,td );
tids.push_back(tid) ;
}
sleep(1) ;
//将这一批线程等待
for( int i =0 ; i <tids.size() ; i++)
{
pthread_join(tids[i] , nullptr) ;
}
return 0 ;
}
分离线程
- 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成内存泄漏。
- 如果不关心线程的返回值,此时可以将该线程进行分离,后续当线程退出时就会自动释放线程资源。
- 一个线程如果被分离了,这个线程依旧要使用该进程的资源,依旧在该进程内运行,甚至这个线程崩溃了一定会影响其他线程,只不过这个线程退出时不再需要主线程去join了,当这个线程退出时系统会自动回收该线程所对应的资源。
可以是线程组内其他线程对目标线程进行分离,也可以是线程自己分离。 - joinable和分离是冲突的,一个线程不能既是joinable又是分离的
pthread_detach,分离线程
int pthread_detach(pthread_t thread);
- thread:被分离线程的ID
return val :
线程分离成功返回0,失败返回错误码
示例:
1、主线程分离
#include<iostream>
#include<vector>
#include<unistd.h>
#include<cstring>
using namespace std ;
#define NUM 3
struct threadData
{
string tid ;
string threadname;
};
string toHex(pthread_t tid)
{
//将tid转为16进制
char buffer[128] ;
snprintf( buffer ,sizeof(buffer) , "0x%x", tid) ;
return buffer ;
}
void *threadRoutine(void *args)
{
int i = 0;
threadData * td = (threadData * )args;
while (i <NUM)
{
sleep(1) ;
i++;
}
delete td;
return nullptr ;
}
void InitThreadData ( threadData * td , int number )
{
td->threadname = "thread-" + to_string( number) ;
}
int main()
{
//创建一批线程
vector<pthread_t > tids ;
for(int i =0 ; i < NUM ;i++)
{
// sleep(1) ;
pthread_t tid ;
//线程是共享堆的
threadData * td =new threadData ;
InitThreadData(td, i);
pthread_create(&tid , nullptr , threadRoutine,td );
tids.push_back(tid) ;
}
sleep(1) ;
//主线程分离
for(auto i : tids)
{
pthread_detach(i) ;
}
//将这一批线程等待
for( int i =0 ; i <tids.size() ; i++)
{
//线程分离后,不能被等待的
int n = pthread_join(tids[i], nullptr);
printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
}
return 0 ;
}
线程分离后,是不能被等待的
示例:
2、新线程自己分离
#include<iostream>
#include<vector>
#include<unistd.h>
#include<cstring>
using namespace std ;
#define NUM 3
struct threadData
{
string tid ;
string threadname;
};
string toHex(pthread_t tid)
{
//将tid转为16进制
char buffer[128] ;
snprintf( buffer ,sizeof(buffer) , "0x%x", tid) ;
return buffer ;
}
void *threadRoutine(void *args)
{
//线程分离
pthread_detach(pthread_self());
int i = 0;
threadData * td = (threadData * )args;
while (i <NUM)
{
sleep(1) ;
i++;
}
delete td;
return nullptr ;
}
void InitThreadData ( threadData * td , int number )
{
td->threadname = "thread-" + to_string( number) ;
}
int main()
{
//创建一批线程
vector<pthread_t > tids ;
for(int i =0 ; i < NUM ;i++)
{
// sleep(1) ;
pthread_t tid ;
//线程是共享堆的
threadData * td =new threadData ;
InitThreadData(td, i);
pthread_create(&tid , nullptr , threadRoutine,td );
tids.push_back(tid) ;
}
sleep(1) ;
//将这一批线程等待
for( int i =0 ; i <tids.size() ; i++)
{
//线程分离后,不能被等待的
int n = pthread_join(tids[i], nullptr);
printf("n = %d, who = 0x%x, why: %s\n", n, tids[i], strerror(n));
}
return 0 ;
}
线程ID&&进程地址空间布局
- pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中,该线程ID和内核中的LWP不是一回事
- 内核中的LWP属于进程调度的范畴,因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
- pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,这个ID属于NPTL线程库的范畴,线程库的后续操作就是根据该线程ID来操作线程的。
- 线程库NPTL提供的pthread_self函数,获取的线程ID和pthread_create函数第一个参数获取的线程ID是一样的。
如何理解pthread_t 的类型
1、Linux不提供真正的线程,只提供LWP,也就是操作系统只需要对内核执行流LWP进行管理,而供用户使用的线程接口等其他数据,应该由线程库自己来管理,因此管理线程时的“先描述,再组织”就应该在线程库里进行
线程库实际上是一个动态库
进程运行时动态库被加载到内存,然后通过页表映射到进程地址空间中的共享区,此时该进程内的所有线程都是能看到这个动态库的
每个线程都有自己私有的栈,其中主线程采用的栈是进程地址空间中原生的栈,而其余线程采用的栈就是在共享区中开辟的。除此之外,每个线程都有自己的struct pthread,当中包含了对应线程的各种属性;每个线程还有自己的线程局部存储,当中包含了对应线程被切换时的上下文数据。
每一个新线程在共享区都有这样一块区域对其进行描述,因此我们要找到一个用户级线程只需要找到该线程内存块的起始地址,然后就可以获取到该线程的各种信息。
除了主线程,所有其他线程的独立栈,都在共享区,具体来讲是在pthread库中,tid指向的用户层tcb中
每一个线程的库级别的tcb的起始地址叫做线程的tid
线程函数pthread_create、pthread_join、pthread_self等,本质都是在库内部对线程属性进行的各种操作,最后将要执行的代码交给对应的内核级LWP去执行就行了,也就是线程数据的管理本质是在共享区的。
pthread_t到底是什么类型取决于实现,但是对于Linux目前实现的NPTL线程库来说,tid本质就是进程地址空间共享区上的一个虚拟地址,同一个进程中所有的虚拟地址都是不同的,因此可以用它来唯一区分每一个线程