线程 【Linux】

news2024/9/28 23:37:54

文章目录

  • 线程
  • 页表
  • POSIX线程库
    • pthread_create
    • 线程等待
      • pthread_join
    • 线程终止
      • pthread_cancel
      • pthread_self
    • 分离线程
  • 线程ID&&进程地址空间布局

线程

  • 在一个程序里的一个执行路线就叫做线程(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

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。
2、线程可以自己调用pthread_exit函数终止自己。
3、一个线程可以调用pthread_cancel函数终止同一进程中的另一个线程

在线程中使用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;
}

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 ;
}

在这里插入图片描述

分离线程

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本质就是进程地址空间共享区上的一个虚拟地址,同一个进程中所有的虚拟地址都是不同的,因此可以用它来唯一区分每一个线程

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1980594.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

聊聊ChatGLM-6B医疗数据微调

前言 参考了多个医疗大模型&#xff0c;如扁鹊、灵心等&#xff0c;重新思考了下微调的方案以及数据集的格式&#xff1b;基于ChatGLM/其它LLM整合多种微调方法的非官方实现的框架&#xff0c;审视其数据集格式&#xff0c;以及调试效果&#xff0c;进行微调。 最终基于liucon…

通配符/泛域名HTTPS证书怎么申请?

通配符SSL证书允许您为一个主域名下的所有次级子域名提供加密连接&#xff0c;这非常适用于拥有多个子域名的网站。以下是申请通配符SSL证书的一般步骤&#xff1a; 一、选择证书类型&#xff1a; 确定需要何种类型的通配符SSL证书&#xff0c;如DV&#xff08;域验证&#x…

黑马头条微服务学习day6-kafka及异步通知文章上下架

文章目录 自媒体文章上下架Kafka概述入门案例分区Kafka高可用设计集群发送类型参数详解消费者详解 SpringBoot集成Kafka传递为消息对象文章上下架功能实现 自媒体文章上下架 Kafka概述 入门案例 &#xff08;1&#xff09;创建kafka-demo项目&#xff0c;导入依赖 <depend…

算法 —— 位运算

目录 位运算常用结论 位运算例题 位1的个数 比特位计算 汉明距离 只出现一次的数字 判定字符是否唯一 丢失的数字 两整数之和 消失的两个数字 进制转换 位运算常用结论 想详细了解位运算的内容可以阅读我的这篇博客&#xff1a;应该背下的位运算 以下我只介绍一些位…

61850 MMS源码(二)

上一篇说了怎么下载&#xff0c;编译和运行mms相关的源码&#xff0c;以及如何抓包。这篇尝试对源码做出一些改动&#xff0c;并实际运行一下。 协议内容厚厚一本书&#xff0c;只是大概看了一下&#xff0c;个人比较习惯从代码入手看逻辑处理&#xff0c;从而理解协议。 我发现…

独立摄影师如何找到自己的第一批客户?

声明&#xff1a;此篇为 ai123.cn 原创文章&#xff0c;转载请标明出处链接&#xff1a;独立摄影师如何找到自己的第一批客户&#xff1f; | AI导航 ai123.cn 嘿&#xff0c;摄影师朋友们&#xff01;咱都知道&#xff0c;想增加目标客户可不简单&#xff0c;推广难、竞争大&am…

Jmeter--http信息头管理器的使用(转载)

本文转载自&#xff1a; Jmeter—什么时候需要配置HTTP信息头管理器以及对应的参数如何输入_信息头管理器中的参数怎么调用-CSDN博客 1、抓包查看Request Headers&#xff08;请求头&#xff09;里Content-Type的信息&#xff0c;如下图&#xff1a; Content-Type的格式为&…

ROS2从入门到精通4-6:路径平滑插件开发案例(以B样条曲线平滑为例)

目录 0 专栏介绍1 ROS2路径平滑器介绍2 平滑器插件编写模板2.1 构造平滑器插件类2.2 注册并导出插件2.3 编译与使用插件 3 基于B样条曲线的路径平滑 0 专栏介绍 本专栏旨在通过对ROS2的系统学习&#xff0c;掌握ROS2底层基本分布式原理&#xff0c;并具有机器人建模和应用ROS2…

了解一下这个基质:粘弹性可编码,organoids培养的好帮手

Dynamic matrices with DNA-encoded viscoelasticity for cell and organoid culture是发表于《nature nanotechnology》的一篇文章&#xff0c;介绍了一种基于DNA的动态交联基质DyNAtrix&#xff0c;用于细胞和类器官培养。DyNAtrix由DNA库与超高分子量聚合物自组装形成&#…

联手体系结构专业委员会:“用户态GPU池化技术”术语发布 | CCF术语快线

本期发布术语热词&#xff1a;用户态GPU池化技术&#xff08;User-space GPU Pooling&#xff09;。 用户态GPU池化技术 作者&#xff1a;陈飞&#xff08;趋动科技&#xff09;张伟韬&#xff08;趋动科技&#xff09;李诚&#xff08;中国科学技术大学&#xff09; 开篇导语…

python使用boto3访问S3对象存储并列出百万级文件对象的存储信息

本文提供了在python3环境里使用boto3访问S3对象存储&#xff0c;并列出百万级文件对象的存储信息的示例代码。 一、测试环境 操作系统和python版本如下&#xff1a; [rootlocalhost boto3]# cat /etc/os-release NAME"openEuler" VERSION"22.03 LTS" I…

【实战指南】轻松上手:部署与应用清华智谱GLM大模型

部署一个自己的大模型&#xff0c;没事的时候玩两下&#xff0c;这可能是很多技术同学想做但又迟迟没下手的事情&#xff0c;没下手的原因很可能是成本太高&#xff0c;近万元的RTX3090显卡&#xff0c;想想都肉疼&#xff0c;又或者官方的部署说明过于简单&#xff0c;安装的时…

GreatSQL 8.0.32-26 今日发布

GreatSQL 8.0.32-26 今日发布 版本信息 发布时间&#xff1a;2024年08月05日 版本号&#xff1a;8.0.32-26, Revision a68b3034c3d 下载链接&#xff1a;https://gitee.com/GreatSQL/GreatSQL/releases/tag/GreatSQL-8.0.32-26 用户手册&#xff1a;https://greatsql.cn/docs…

【知识专栏丨python数分实战】天猫订单数据分析及可视化|taobao天猫订单接口

今天这篇文章将给大家介绍天猫订单数据分析及可视化案例。 import pandas as pdimport numpy as npfrom pyecharts.charts import Pie,Bar,Line,Map,Map3D,Funnelfrom pyecharts import options as optsimport matplotlib.pyplot as pltimport warningsimport seaborn as snsfr…

《刚刚问世》系列初窥篇-Java+Playwright自动化测试-7-元素基础定位方式-下篇 (详细教程)

软件测试微信群&#xff1a;https://bbs.csdn.net/topics/618423372 有兴趣的可以扫码加入 1.简介 上一篇主要是讲解我们日常工作中在使用Playwright进行元素定位的一些比较常用的基础定位方式的理论基础知识以及在什么情况下推荐使用。今天这一篇讲解和分享一下剩下部分的基…

重塑未来体验:边缘计算与云原生的完美邂逅

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f3c5;个人专栏&#xff1a;《未来已来&#xff1a;云原生之旅》&#x1f3c5; &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、引言 1、云原生的兴起 2、边缘计算的兴起 二、边缘计算基础 …

LoadRunner12 添加事务并添加检查点

1、先要添加事务开始函数lr_start_transaction("登陆事务");&#xff0c;在接口上方右击点击-插入-开始事务。输入事务名称&#xff1b; 2、在某个接口想法 右击点击-插入-结束事务&#xff0c;输入事务名称&#xff0c;与开始事务名称要保持一致&#xff0c;lr_end_…

springboot自动装配(源码分析)

利用spi机制发现配置类并注册到spring容器中 以下示例使用springboot:3.2.1版本 相关注解 SpringBootApplication EnableAutoConfiguration AutoConfigurationImportSelector 使用Import导入AutoConfigurationImportSelector&#xff0c;随着springboot启动&#xff0c;会…

工业控制常用的EtherNet/IP、OPC UA协议的标签数据转发到另外的PLC寄存器地址

在工业自动化领域&#xff0c;越来越多的碰到标签方式通讯的设备&#xff0c;常用有CIP(基于EtherNet/IP) 的协议、OPCUA协议等&#xff0c;CIP协议主要是罗克韦尔/AB的PLC、欧姆龙NX/NJ系列的PLC等&#xff0c;OPCUA协议常见于工业机器人、智能焊接设备等。在不具备标签协议接…

AI绘画变现也有新思路,国风带你日进斗金!

在中国的文化传承中&#xff0c;古典的风韵总是能引发无尽的遐想和美感。 在现代化的今天&#xff0c;越来越多的人开始重新审视和欣赏那些古老的中国风&#xff0c;发现其中蕴含的深厚文化底蕴与无与伦比的美感。 特别是在影视、音乐、舞蹈等艺术形式中&#xff0c;国风元素…