• 多线程(pthread库)


    POSIX线程库

    引言

    前面我们提到了Linux中并无真正意义上的线程
    从OS角度来看,这意味着它并不会提供直接创建线程的系统调用,它最多给我们提供创建轻量级进程LWP的接口
    但是从用户的角度来看,用户只认识线程啊!
    因此,操作系统OS与用户两者之间,必定存在一个桥梁——库
    这个线程库,对下能够将Linux提供的LWP进程接口进行封装,对上能够给用户进行线程控制的接口
    这个库,我们就称作pthread库,在里面的绝大多数函数的名字都是以“pthread_”打头
    在任何linux系统下,不管版本的老旧,都会默认自带,是一个原生线程库,等下我们也会进行验证

    前提

    但是,pthread线程库,并非随意就能使用,还需要我们编写代码时,进行一些附加操作

    1.要使用这些函数库,要引入头文件
    2.链接这些线程函数库时要使用编译器命令的“-lpthread”选项

      1 mythreadTest:threadTest.cc
      2   g++ -o $@ $^ -std=c++11 -lpthread                                                                                                                                 
      3 .PHONY:clean
      4 clean:
      5   rm -f mythreadTest
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    线程创建

    就像每个文件都有着其对应的inode编号,每个线程也有着自己的编号,它的类型时pthread_t类型
    假如在编译器中一直跳转,寻找它最开始的定义
    在这里插入图片描述
    可以发现,它实际上就是一个unsigned long类型
    不过具体这个编号有什么用,我们先按下不表
    我们先介绍创建线程提供的pthread_create函数
    man手册查该函数,会给出相应的函数介绍
    可以看到该函数位于3号手册中,所以也符合我们前面的讲解,即该函数不是系统调用的函数,而是封装了linux系统的轻量级进程接口的函数
    功能是创建一个新线程(create a new thread)
    在这里插入图片描述
    总共有4个参数

    第一个参数thread
    是线程id的地址(返回线程ID)
    第二个参数attr,
    设置线程的属性,attr为NULL表示使用默认属性,通常使用的时候都给nullptr,使用默认属性
    第三个参数start_routine
    是个函数地址,线程启动后要执行的函数,参数是void*,返回参数也是void*,是一个函数指针
    第四个参数arg
    是等下传给start_routine的参数(传给线程启动函数的参数)

    有了上面的基础后,我们就可以先简单创建一个我们的线程
    主线程输出对应的线程id
    另外一个线程输出自己正在允许

        1 #include <iostream>
        2 #include <unistd.h>
        3 
        4 using namespace std;
        5 void* thread_run(void* args)
        6 {
        7   while(true)
        8   {
        9     cout << "new thread is running" << endl;
       10     sleep(1);                                                                                                                                                     
       11   }
       12 }
       13 
       14 int main()
       15 {
       16   pthread_t t;
       17   pthread_create(&t,nullptr,thread_run,nullptr);
       18 
       19   while(true)
       20   {
       21     cout << "main thread is running,thread id : " << t << endl;
       22     sleep(1);
       23   }
       24   return 0;
       25 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到,结果符合我们的预期
    在这里插入图片描述
    往命令行窗口输入lld + 对应文件名,即可看到该文件链接了什么库
    可以看到,其中有一个pthread库,它对应的路径是/lib64/libpthread,也就和我们之前所说的任何Linux系统默认自带相应的pthread库说法,完美符合
    在这里插入图片描述
    但是我们现在只是创建了一个线程而已,所以,我们的代码肯定还是要改进,创建更多的线程的
    我们采取数组的方式,我们知道数组名实际上就是首元素的地址,加上对应的i,实际对应的刚好就是数组里面每个元素的地址,而不用再取地址&
    并且,我们可以开始研究第四个参数arg,它是主线程往新线程里面传的参数
    那实际上能不能传过去呢?我们对传进来的参数args进行强制类型转换,然后打印相应的内容,假如能够打印相应的内容,则说明args这个参数的确能够是主线程往新线程里面传的参数

      1 #include <iostream>
      2 #include <unistd.h>
      3 #define NUM 10
      4 using namespace std;
      5 void* thread_run(void* args)
      6 { 
      7   char* name = (char*)args;
      8 
      9   while(true)
     10   {
     11     cout << "new thread: " << name << " is running" << endl;
     12     sleep(1);
     13   }
     14 
     15   return nullptr;                                                                                                                                                   
     16 }
     17 
     18 int main()
     19 {
     20   pthread_t tid[NUM];
     21   for (int i = 0;i < NUM;i++)
     22   {
     23      char tname[64];
     24      snprintf(tname,sizeof(tname),"thread-%d",i + 1);
     25      pthread_create(tid + i,nullptr,thread_run,tname);
     26   }
     27 
     28 
     29   while(true)
     30   {
     31     cout << "main thread is running" << endl;
     32     sleep(1);
     33   }
     34   return 0;
     35 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    但是,打印出来的结果,却不符合我们的预期
    第一,我们预想的是,每个线程的编号都应该不同,即每个线程的名字都不一样,毕竟我们循环往tname这个数组里面放内容的时候,用的是不同的i
    第二,有部分线程输出代码紧挨在一起,并且主线程并不是最先运行的,反而是新线程先运行
    在这里插入图片描述
    对于第二个问题,我们其实可以解释,在进程的一章中我们就已经提到过,哪个进程先被调度,其实是不确定的,同样的,线程也是我们调用轻量级进程接口创建出来的,肯定也是符合这个规律,所以谁先被调度,完全取决于调度器决定,先创建的线程,不一定被调度
    对于第一个问题,就有点难理解
    实际上是由于线程共享的是同一份资源,即便这只是一个临时变量
    因此,tname里面存的地址,在不同线程看来都是相同的
    所以往里面同时写数据,就会覆盖原有tname空间的旧内容
    最后剩下的,仅仅是最后调度的线程的名字
    那我们要怎么修改呢?
    一种简单的方式,就是直接new相应的空间
    (不过要注意,此时使用snprintf函数的时候,就不能再直接sizeof,这样计算的就单纯只会是指针的大小,所以这里直接指定64字节,毕竟整个空间也就64字节)
    对于每个线程来说,都会new出新的自己的空间,这样放的数据就不会再被覆盖
    相当于每个线程,都有了自己的房子,从此井水不犯河水

      1 #include <iostream>
      2 #include <unistd.h>
      3 #define NUM 10
      4 using namespace std;
      5 void* thread_run(void* args)
      6 {
      7   char* name = (char*)args;
      8 
      9   while(true)
     10   {
     11     cout << "new thread: " << name << " is running" << endl;
     12     sleep(1);
     13   }
     14   delete name;                                                                                                                                                      
     15   return nullptr;
     16 }
     17 
     18 int main()
     19 {
     20   pthread_t tid[NUM];
     21   for (int i = 0;i < NUM;i++)
     22   {
     23      char* tp = new char[64];
     24      snprintf(tp,64,"thread-%d",i + 1);
     25      pthread_create(tid + i,nullptr,thread_run,tp);
     26   }
     27 
     28 
     29   while(true)
     30   {
     31     cout << "main thread is running" << endl;
     32     sleep(1);
     33   }
     34   return 0;
     35 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36

    经过修改后的运行效果,就符合我们的预期了
    在这里插入图片描述

    线程终止

    前面我们提到了在linux系统下,是没有对应具体线程的实现!而是采用复用的方式
    所以,进程有的特性,线程往往也会有

    主线程提前退出

    假如主线程现在提前退出了,说不再和其它新线程一起玩,会发生什么情况呢?
    将上面主线程的代码修改一下,把循环去掉
    在这里插入图片描述
    此时,再编译运行我们的代码,会得到下面的结果
    可以看到,一旦主线程退出了,其它的所有新线程,就会全部强制退出
    在这里插入图片描述
    为什么会出现这种情况呢?
    就是因为线程是进程的一个执行分支,线程异常了,发送信号是给进程发信号,进程挂了,所有依附于它的线程,全部都走不了,覆巢之下,安有完卵,指的就是这个道理
    同样的,假如其中一个线程调用了exit函数,那请问最后的结果会是怎么样呢?
    我们同样可以修改相应的代码,在循环中加入相应的exit函数
    在这里插入图片描述
    可以看到,只有几个线程成功输出了自己的编号,程序就自动停止了
    所以实际的情况就是,有几个线程成功被创建,但是其中有一个线程执行exit函数,然后全部线程都挂掉了
    在这里插入图片描述
    只要有任何一个线程调用exit函数,整个进程中,所有的线程都会全部退出
    关键不是主线程还是新线程的问题,而是大家都是一体的,同生共死

    阻塞等待

    正是由于线程和进程是有很多相似之处的,进程有父进程阻塞等待,回收子进程的操作
    线程也会有相应的概念
    主线程需要等待子线程,然后进行相应的回收,否则子线程就会陷入僵尸状态
    在pthread库里面就已经提供了相应主线程等待的库函数pthread_join
    调用该函数,主线程就会阻塞,并回收相应退出的新线程(join with a terminated thread)
    在这里插入图片描述
    它总共有两个参数‘
    第一个参数thread,就是我们之前提到过的线程id
    第二个参数retval,是一个二级指针void**,为什么是二级指针呢?
    因为它是一个输出型参数,早在C语言函数中我们就已经学过,由于C语言中没有引用的概念,因此,在函数内部进行赋值,其实改变的都是形参,并不会改变实参
    想要改变实参,就需要传相应的指针
    想传int出来,就要int*
    想传int出来,就要int**作为参数
    同理,假如我们返回的参数此时是一个void
    类型的,那用void**接收,也就非常合理了
    在这里插入图片描述
    它的返回值和前面提到过的pthread_create函数相同
    成功的话,就返回0;否则,返回一个错误的数字

      1 #include <iostream>
      2 #include <unistd.h>
      3 #define NUM 10
      4 using namespace std;
      5 void* thread_run(void* args)
      6 { 
      7   char* name = (char*)args;
      8 
      9   while(true)
     10   {
     11     cout << "new thread: " << name << " is running" << endl;
     12     sleep(1);
     13   }                                                                                                                                                             
     14   delete name;                                                                                                                                                  
     15   return nullptr;                                                                                                                                               
     16 }                                                                                                                                                               
     17                                                                                                                                                                 
     18 int main()                                                                                                                                                      
     19 {                                                                                                                                                               
     20   pthread_t tid[NUM];                                                                                                                                           
     21   for (int i = 0;i < NUM;i++)                                                                                                                                   
     22   {                                                                                                                                                             
     23      char* tp = new char[64];                                                                                                                                   
     24      snprintf(tp,64,"thread-%d",i + 1);                                                                                                                         
     25      pthread_create(tid + i,nullptr,thread_run,tp);                                                                                                             
     26   }                                                                                                                                                                 
     27 
     28   for (int i = 0;i < NUM;i++)
     29   {
     30     pthread_join(tid[i],nullptr);
     31   }
     32 
     33   return 0;
     34 }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    终止方式

    既然,我们知道主线程,需要阻塞等待子线程退出,并回收相应的子线程
    那了解子线程有多少种退出方式,就非常有必要
    子线程总共有三种退出方式
    第一种方式,线程函数执行完毕,此时直接返回nullptr,线程就会退出

      1 #include <iostream>
      2 #include <unistd.h>
      3 #define NUM 10
      4 using namespace std;
      5 void* thread_run(void* args)
      6 {                                                                                                                                                                   
      7   char* name = (char*)args;
      8 
      9   while(true)
     10   {
     11     cout << "new thread: " << name << " is running" << endl;
     12     sleep(1);
     13     break;
     14   }
     15   delete name;
     16   return nullptr;
     17 }
     18 int main()
     19 {                                                                                                                                    
     20   pthread_t tid[NUM];                                                                                                                
     21   for (int i = 0;i < NUM;i++)                                                                                                        
     22   {                                                                                                                                  
     23      char* tp = new char[64];                                                                                                        
     24      snprintf(tp,64,"thread-%d",i + 1);                                                                                              
     25      pthread_create(tid + i,nullptr,thread_run,tp);                                                                                  
     26   }                                                                                                                                  
     27                                                                                                                                      
     28   for (int i = 0;i < NUM;i++)                                                                                                        
     29   {                                                                                                                                  
     30     int n = pthread_join(tid[i],nullptr);                                                                                            
     31     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响                                                                    
     32     if(n!= 0)   cerr << "pthread_join error" << endl;                                                                                
     33   }                                                                                                                                  
     34                                                                                                                                      
     35   return 0; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    第二种方式,pthread库里面提供了相应的线程退出函数pthread_exit
    在这里插入图片描述
    它的参数retval,为一个输出型参数
    没错,和我们之前提到的pthread_join的参数名字是相同的,也就意味着两者肯定有所关联
    通过返回retval,我们对应的主线程就可以接收到对应的错误信息
    那为什么我们不通过设置全局变量errno来输出对应的错误信息呢?
    因为不同线程对于这个全局变量是共享的,全部线程都同时使用一个全局变量,就可能会出现覆盖等等问题,导致出错了也可能不知道
    因此,pthreads函数出错时不会设置全局变量errno(而大部分其他POSIX函数会这样做)
    而是将错误码通过返回值返回
    还有一个好处是,对于pthreads函数的错误,通过返回值判定,要比读取线程内的errno变量的开销更小

      1 #include <iostream>
      2 #include <unistd.h>
      3 #define NUM 10
      4 using namespace std;
      5 void* thread_run(void* args)
      6 { 
      7   char* name = (char*)args;
      8 
      9   while(true)
     10   {
     11     cout << "new thread: " << name << " is running" << endl;
     12     break;
     13   }
     14   delete name;
     15   pthread_exit(nullptr);                                                                                                                                            
     16 }
     17 
     18 
     19 int main()
     20 {
     21   pthread_t tid[NUM];
     22   for (int i = 0;i < NUM;i++)
     23   {
     24      char* tp = new char[64];
     25      snprintf(tp,64,"thread-%d",i + 1);
     26      pthread_create(tid + i,nullptr,thread_run,tp);
     27   }
     28 
     29   for (int i = 0;i < NUM;i++)
     30   {
     31     int n = pthread_join(tid[i],nullptr);
     32     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响
     33     if(n!= 0)   cerr << "pthread_join error" << endl;
     34   }
     35   cout << "all thread quit" << endl;
     36   return 0;
     37 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
      1 #include <iostream>
      2 #include <unistd.h>
      3 #include <pthread.h>
      4 #include <string>
      5 #include <ctime>
      6 #define NUM 10
      7 using namespace std;
      8 
      9 
    
     10 class ThreadData
     11 {
     12 public:
     13   ThreadData(const string& name,int id,time_t createTime):_name(name),_id(id),_createTime((uint64_t)createTime)
     14   {}
     15   ~ThreadData()
     16   {}
     17 public:
     18     string _name;
     19     int _id;
     20     uint64_t _createTime;
     21 };
    
     22 void* thread_run(void* args)
     23 {
     24   ThreadData* tp = static_cast<ThreadData*>(args);
     25 
     26   while(true)
     27   {
     28     cout << "thread is running,name: " << tp->_name << " create time: "<< tp->_createTime << " index:" << tp->_id << endl;                                          
     29     break;
     30   }
     31   delete tp;
     32   pthread_exit((void*)2);
     33 }
     34 
     35 
     36 int main()
     37 {
     38   pthread_t tid[NUM];
     39   for (int i = 0;i < NUM;i++)
     40   {  
     41      char tname[64];
     42      snprintf(tname,sizeof(tname),"thread-%d",i + 1);
     43      ThreadData* tp = new ThreadData(tname,i + 1,time(nullptr));
     44      pthread_create(tid + i,nullptr,thread_run,tp);
     45   }
     46 
    
     47   void* ret = nullptr;
     48   for (int i = 0;i < NUM;i++)
     49   {
     50     int n = pthread_join(tid[i],&ret);
     51     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响
     52     if(n!= 0)   cerr << "pthread_join error" << endl;
     53 
     54     cout << "thread quit: " << (uint64_t)ret << endl;
     55   }
     56   cout << "all thread quit" << endl;
     57   return 0;
     58 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62

    最后一种方式,是一个线程可以调用pthread_ cancel函数终止同一进程中的另一个线程
    在这里插入图片描述
    整个函数只需要一个参数,也即是我们的线程id
    它的功能就是取消一个执行中的线程
    成功返回0;反之,则返回错误码

    类型转换

    在C,或者C++中,我们都知道,一个类型的值赋值给不匹配的类型变量,就会发生报错
    但是,我们仔细思考一下,在计算机的眼里,不同数据有区别吗?
    都只是0,1的集合罢了
    所以,报错是编译器检测发现你类型不匹配,然后报错,显示无法编译你的代码
    所谓的类型转换,就是让我们骗过编译器,让数据能够赋到我们想要的变量中
    比如说下面的代码,1还是那个1,但是是int类型
    你需要将它类型转换,告诉编译器,这个1其实是一个地址,这样才能成功赋值

    void* ret = (void*)1;

    进一步思考的话,类型转换也告诉了OS,这究竟是什么类型变量
    这非常关键,决定我们将它存到哪里,它的偏移地址是什么等等,这样我们以后才能成功访问到这个数据

    void*

    所以,为什么无论是我们pthread_create函数,还是我们的pthread_exit函数,它们的参数中,设计的都是void*
    为的是什么?
    为的就是我们让我们传入参数和返回参数的可塑性更强,它并非局限我们只能传一个字符串作为线程函数传入参数,或者只能返回对应的错误码
    我们是可以传int,double*等等所有的指针,甚至我们是可以传对象指针进去!!!*
    只需要void*接收,然后再类型转换为我们想要的类型,就可以让OS找到对应的资源!!!
    下面这段代码,就实现了传一个对象进去线程函数里面,并且通过返回这个对象的指针,将里面处理好的结果带出来

    整段代码实现的功能
    就是让不同的线程,分别实现从1到对应top数字的求和
    原本的串行执行,转变为现在的并发执行

      1 #include <iostream>
      2 #include <unistd.h>
      3 #include <pthread.h>
      4 #include <string>
      5 #include <ctime>
      6 #define NUM 10
      7 using namespace std;
      8 enum{ ERROR = 0,OK };
      9 
     10 class ThreadData
     11 {
     12 public:
     13   ThreadData(const string& name,int id,time_t createTime,int top):_name(name),_id(id),_createTime((uint64_t)createTime),_status(OK),_top(top),_result(0)
     14   {}
     15   ~ThreadData()
     16   {}
     17 public:
     18     //传入的参数
     19     string _name;
     20     int _id;
     21     uint64_t _createTime;
     22     
     23     //返回的参数
     24     int _status; //该线程的参数
     25     int _top;
     26     int _result; //结果是什么
     27     //char arr[n];
     28 };
     29 void* thread_run(void* args)
     30 {                                                                                                                                                                   
     31   ThreadData* tp = static_cast<ThreadData*>(args);
     32 
     33   for (int i = 1;i <= tp->_top;i++)
     34   {
     35       tp->_result += i;
     36   }
     37 
     38   cout << "tp->_name: " << tp->_name << endl;
     39   return tp;
     40 }
     41 
     42 int main()
     43 {
     44   pthread_t tid[NUM];
     45   for (int i = 0;i < NUM;i++)
     46   {  
     47      char tname[64];
     48      snprintf(tname,64,"thread-%d",i + 1);
     49      //多传入一个参数,用来在创建线程,执行相应任务所加到的对应的数字
     50      ThreadData* tp = new ThreadData(tname,i + 1,time(nullptr),100 + 4*i);
     51      pthread_create(tid + i,nullptr,thread_run,tp);
     52      sleep(1);
     53   }
     54 
     55   void* ret = nullptr;
     56   for (int i = 0;i < NUM;i++)
     57   {
     58     int n = pthread_join(tid[i],&ret);
     59     //errno变量只有一个,而线程有多个,作同时修改,可能会互相影响
     60     if(n!= 0)   cerr << "pthread_join error" << endl;
     61     ThreadData* tp = static_cast<ThreadData*> (ret);
     62     if (tp->_status == OK)
     63     {
     64        cout << "thread name: " << tp->_name << " 计算的结果为:" << tp->_result << "[0," << tp->_top << "]" << endl;
     65     }
     66     delete tp;
     67   }
     68   cout << "all thread quit" << endl;
     69   return 0;
     70 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71

    输出的结果如下图所示:
    在这里插入图片描述

    让线程获取自己的线程id

    那线程有自己的编号,能不能让线程获取对应自己的编号呢?
    pthread库中也提供了相应的接口pthread_self
    在这里插入图片描述
    函数参数是没有的,直接调用即可输出当前线程的id是什么
    我们可以编写一段程序,来看看对应的线程id,同时返回到主线程,也打印出来对比一下

      1 #include <iostream>
      2 #include <unistd.h>
      3 #include <pthread.h>
      4 #include <string>
      5 #include <ctime>
      6 #define NUM 10
      7 using namespace std;
      8 
      9 void* thread_create(void* args)
     10 {
     11    const char* name = static_cast<const char*>(args);
     12    int cnt = 5;
     13    while(cnt--)
     14    {
     15      cout << name << " is running..." << "  obtain my tid: " << pthread_self()<< endl;                                                                              
     16      sleep(1);
     17    }
     18 
     19    pthread_exit((void*)11);
     20 }
     21 int main()
     22 {
     23   pthread_t tid;
     24   pthread_create(&tid,nullptr,thread_create,(void*)"thread 1");
     25 
     26   void* ret = nullptr;
     27   int n = pthread_join(tid,&ret);
     28   if (n != 0)  cerr << "thread_join error: " << endl;
     29   cout << "new thread exit: " << (uint64_t)ret << endl;
     30   cout << " quit thread id: " << tid << endl;
     31   return 0;
     32 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    可以看到线程的id通过pthread_self函数,是能够成功获取的
    在这里插入图片描述

    分离线程

    前面我们提到,主线程会阻塞等待新线程退出
    但是阻塞等待,也就意味着在这期间,主线程并不能干任何事情
    那假如我们想要主线程不阻塞等待,让新线程自己回收自己,又应该怎么操作呢?
    pthread库中实际上,确实提供类似的接口函数pthread_detach
    默认情况下,新创建的线程是joinable的,线程退出后,需要对其进行pthread_join操作,否则无法释放资源,从而造成系统泄漏
    但假如我们不关心线程的返回值,join是一种负担,此时,我们就可以修改对应线程的属性,让线程自己退出时,自动释放资源

    PS:假如一个线程分离detach后,此时就不能再join了,函数会发生报错
    joinable和分离是冲突的,一个线程不能既是joinable又是分离的

    下面,我们简单写一段代码来验证joinable和分离,两者是冲突的这个结论

      1 #include <iostream>
      2 #include <pthread.h>
      3 #include <unistd.h>
      4 #include <cstring>
      5 #include <string>
      6 using namespace std;
      7 void* threadRoutine(void* args)
      8 {
      9   string name = static_cast<const char*>(args);
     10   int cnt = 5;
     11   while(cnt)
     12   {
     13     cout << name << " : " << cnt-- << endl;
     14     sleep(1);
     15   }
     16   return nullptr;
     17 }
     18 int main()
     19 {
     20   pthread_t tid;
     21   pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");
     22   pthread_detach(tid);                                                                                                                                              
     23   int n = pthread_join(tid,nullptr);
     24   if(0 != n)
     25   {
     26     cerr << "error: " << n << " : "<< strerror(n) << endl;
     27   }
     28   return 0;
     29 
     30 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    我们再创建一个新线程后,再detach掉对应的新线程
    可以看到运行结果显示,程序会直接发生报错
    在这里插入图片描述
    但除了在主线程进行detach外,也可以在新线程中,让新线程自己detach
    比如说下面的代码

      1 #include <iostream>
      2 #include <pthread.h>
      3 #include <unistd.h>
      4 #include <cstring>
      5 #include <string>
      6 using namespace std;
      7 void* threadRoutine(void* args)
      8 {
      9   pthread_detach(pthread_self());
     10   string name = static_cast<const char*>(args);
     11 
     12   int cnt = 5;
     13   while(cnt)
     14   {
     15     cout << name << " : " << cnt-- << endl;
     16     sleep(1);
     17   }
     18   return nullptr;
     19 }
     20 int main()
     21 {
     22   pthread_t tid;
     23   pthread_create(&tid,nullptr,threadRoutine,(void*)"thread 1");
     24   int n = pthread_join(tid,nullptr);                                                                                                                                
     25   if(0 != n)
     26   {
     27     cerr << "error: " << n << " : "<< strerror(n) << endl;
     28   }
     29   return 0;
     30 
     31 }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    但是运行的结果却并不符合预期,我们并没有看到报错,可以发现新线程照样可以正常跑
    在这里插入图片描述
    这是为什么呢?
    原因就在于我们刚开始说的,线程谁先执行并不确定,线程可能被创建出来,但是并没有运行
    在上述的代码中就是如此,新线程虽然被创建了,但是并没有被允许
    此时主线程检测新线程的属性,可以发现仍然是joinable的,然后允许相关的join代码,主线程被挂起,此时新线程才被执行
    因此,假如我们要让新线程自己释放自己的资源的话,我们还需要先让主线程sleep上对应的秒数,让新线程先执行
    在这里插入图片描述
    此时允许的结果就符合我们之前的说法了
    在这里插入图片描述
    正是由于这个的缘故,因此我们一般分离线程,采取的方式都是建议主线程直接detach,而不是自detach

  • 相关阅读:
    前端学习笔记--面试题系列总结
    linux非root安装特定版本的cuda
    GPT如何避免从入门到放弃(一)——认识GPT
    MySQL 中的 sql_mode 选项以及配置
    【附源码】计算机毕业设计JAVA宠物领养管理系统
    使用AVX2指令集加速推荐系统MMR层余弦相似度计算
    vue视频播放功能
    如何获得淘宝商品详情高级版 API 数据接口
    系列文章|云原生时代下微服务架构进阶之路 - Snap-E
    算法题:牛牛的三元组问题
  • 原文地址:https://blog.csdn.net/m0_72873854/article/details/133300683