Linux:42线程控制lesson30

[复制链接]
发表于 2025-9-21 19:46:57 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
代码1:验证join可以去的线程实行完后的退出码/返回值
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<pthread.h>
  4. #include<string>
  5. using namespace std;
  6. void* routine(void* arg){
  7.     string name = static_cast<const char*>(arg);
  8.     int cnt = 5;
  9.     while(cnt--)
  10.     {
  11.         cout<<"我是子线程,名字:"<<name<<endl;
  12.         sleep(1);
  13.     }
  14.     return (void*)10;
  15. }
  16. int main(){
  17.     pthread_t tid;
  18.     pthread_create(&tid,nullptr,routine,(void*)"thread-1");
  19.     int cnt = 5;
  20.     while(cnt--){
  21.         cout<<"我是主线程"<<endl;
  22.         sleep(1);
  23.     }
  24.     void* ret = nullptr;
  25.     pthread_join(tid,&ret);
  26.     cout<<"子进程退出,退出码为:"<<(long long)ret<<endl;
  27. }
复制代码
 

1.main函数竣事,代表主线程竣事,也代表历程竣事
2.新线程对应的入口函数,运行竣事,代表当前线程运行竣事。
3.问题:给线程通报的参数和返回值,可以是恣意范例 

代码2:30min证实::给线程通报的参数和返回值,可以是恣意范例 ,下面的例子是类范例
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<pthread.h>
  4. #include<string>
  5. using namespace std;
  6. class Task{
  7.     public:
  8.         Task(int a,int b)
  9.             :_a(a)
  10.             ,_b(b)
  11.             {}
  12.         int Cal(){
  13.             return _a+_b;
  14.         }
  15.         ~Task() {}
  16.     private:
  17.         int _a;
  18.         int _b;
  19. };
  20. class Result{
  21.     public:
  22.         Result(int result)
  23.             :_result(result)
  24.             {}
  25.         int getresult(){
  26.             return _result;
  27.         }
  28.         ~Result(){}
  29.     private:
  30.         int _result;
  31. };
  32. void* routine(void* arg){
  33.     Task*t = static_cast<Task*>(arg);
  34.     sleep(1);
  35.     Result* r = new Result(t->Cal());
  36.     sleep(1);
  37.     return r;
  38. }
  39. int main(){
  40.     pthread_t tid;
  41.     Task* t = new Task(20,10);
  42.     pthread_create(&tid,nullptr,routine,t);
  43.     Result* ret = nullptr;
  44.     pthread_join(tid,(void**)&ret);
  45.     int n = ret->getresult();
  46.     cout<<"子线程的返回值是:"<<n<<endl;
  47. }
复制代码
 

线程停止 

假如必要只终⽌某个线程⽽不终⽌整个历程,可以有三种⽅法:
1. 从线程函数return。这种⽅法对主线程不适⽤,从main函数return相称于调⽤exit。
2. 线程可以调⽤pthread_exit终⽌⾃⼰。
3. ⼀个线程可以调⽤pthread_cancel终⽌同⼀历程中的另⼀个线程。
 
pthread_exit:线程停止


  1. 功能:线程终⽌
  2. 原型:
  3. void pthread_exit(void *value_ptr);
  4. 参数:
  5. value_ptr:value_ptr不要指向⼀个局部变量,就是指的一个变量
  6. pthread_exit 的参数 value_ptr 是一个指向某个值的指针,该值将作为线程的返回值。这个返回值可以被主线程通过 pthread_join 获取。
  7. 返回值:
  8. ⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)
复制代码
和return是等价的。 
线程期待 :pthread_join

为什么必要线程期待?

• 已经退出的线程,其空间没有被释放,仍然在历程的地址空间内。用来释放空间
• 创建新的线程不会复⽤刚才退出线程的地址空间。
  1. 功能:等待线程结束
  2. 原型
  3. int pthread_join(pthread_t thread, void **value_ptr);
  4. 参数:
  5. thread:线程ID
  6. value_ptr:它指向⼀个指针,后者指向线程的返回值
  7. 返回值:成功返回0;失败返回错误码
复制代码
 历程以差别的方式停止,pthread_join得到的线程返回值是不一样的

调⽤该函数的线程将挂起期待,直到id为thread的线程终⽌。thread线程以差别的⽅法终⽌,通过 pthread_join得到的终⽌状态是差别的,总结如下:
  1. 1. 如果thread线程通过return返回,
  2. value_ptr所指向的单元⾥存放的是thread线程函数的返回值。
  3. 2. 如果thread线程被别的线程调⽤pthread_cancel异常终掉,
  4. value_ptr所指向的单元⾥存放的是常 数PTHREAD_CANCELED。
  5. 3. 如果thread线程是⾃⼰调⽤pthread_exit终⽌的,
  6. value_ptr所指向的单元存放的是传给 pthread_exit的参数。
  7. 4. 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ptr参数。
复制代码
pthread_cancel:取消线程 


参数:tid 
主线程取消新线程
取消的时候肯定要包管:新线程已经启动 
 线程被取消,返回的结果是-1
表明:线程假如被取消退出的结果是-1

最保举的还是return, 

线程分离 :pthread_detach(tid)

• 默认情况下,新创建的线程是joinable的,线程退出后,必要对其进⾏pthread_join操纵,否则 ⽆法释放资源,从⽽造成系统走漏。
• 假如不关⼼线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃ 动释放线程资源。
假如主线程不想再关心新线程,而是当新线程竣事的时候,让他本身释放??
  1. 设置新线程为分离状态
  2. 技术层面:  线程默认是需要被等待的,joinable。如果不想让主线程等待新线程
  3. 想让新线程结束之后,自己退出,设置为分离状态(!joinable or detach)  // todo
  4. 理解层面:线程分离,主分离新,新把自己分离。
  5. 分离的线程,依旧在进程的地址空间中,进程的所有资源,被分离的线程,依旧可以访问,可以操作。
  6. 主不等待新线程。
  7. 分离操作
  8. 如果线程被设置为分离状态,不需要进行join,join会失败!!
复制代码
主线程设置分离
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<pthread.h>
  4. #include <cstring>
  5. using namespace std;
  6. void* routine(void* arg){
  7.     string name = static_cast<const char*>(arg);
  8.     int cnt = 5;
  9.     while(cnt--)
  10.     {
  11.         cout<<"我是子线程,名字:"<<name<<endl;
  12.         sleep(1);
  13.     }
  14. }
  15. int main(){
  16.     pthread_t tid;
  17.     pthread_create(&tid,nullptr,routine,(void*)"thread-1");
  18.     //线程分离
  19.     pthread_detach(tid);
  20.     int cnt = 5;
  21.     while(cnt--){
  22.         cout<<"我是主线程"<<endl;
  23.         sleep(1);
  24.     }
  25.     int n = pthread_join(tid,nullptr);
  26.    
  27.     if(n!=0){
  28.         cout<<"pthread_join error"<<endl;
  29.     }else{
  30.         cout<<"pthread_join success"<<endl;
  31.     }
  32. }
复制代码
 一个被分离的历程,调用join会失败。
pthread_join()失败,就会返回0。
线程本身设置分离
  1. #include<iostream>
  2. #include<unistd.h>
  3. #include<pthread.h>
  4. #include <cstring>
  5. using namespace std;
  6. void* routine(void* arg){
  7.     pthread_detach(pthread_self());
  8.     string name = static_cast<const char*>(arg);
  9.     int cnt = 5;
  10.     while(cnt--)
  11.     {
  12.         cout<<"我是子线程,名字:"<<name<<endl;
  13.         sleep(1);
  14.     }
  15.     return nullptr;
  16. }
  17. int main(){
  18.     pthread_t tid;
  19.     pthread_create(&tid,nullptr,routine,(void*)"thread-1");
  20.    
  21.     int cnt = 5;
  22.     while(cnt--){
  23.         cout<<"我是主线程"<<endl;
  24.         sleep(1);
  25.     }
  26.     int n = pthread_join(tid,nullptr);
  27.    
  28.     if(n!=0){
  29.         cout<<"pthread_join error"<<endl;
  30.     }else{
  31.         cout<<"pthread_join success"<<endl;
  32.     }
  33. }
复制代码
 

创建多线程
  1. #include <iostream>
  2. #include <string>
  3. #include <vector>
  4. #include <unistd.h>
  5. #include <pthread.h>
  6. #include <cstdio>
  7. #include <cstring>
  8. 创建多线程
  9. const int num = 10;
  10. void *routine(void *args)
  11. {
  12.      sleep(1);
  13.      std::string name = static_cast<const char *>(args);
  14.      delete (char*)args;
  15.      int cnt = 5;
  16.      while (cnt--)
  17.      {
  18.          std::cout << "new线程名字: " << name << std::endl;
  19.          sleep(1);
  20.      }
  21.      return nullptr;
  22. }
  23. int main()
  24. {
  25.      // char id[64];多执行流访问同一份资源,有危险。
  26.      std::vector<pthread_t> tids;
  27.      for (int i = 0; i < num; i++)
  28.      {
  29.          pthread_t tid;
  30.          // bug??
  31.          char *id = new char[64];//需要这么创建
  32.          snprintf(id, 64, "thread-%d", i);
  33.          int n = pthread_create(&tid, nullptr, routine, id);
  34.          if (n == 0)
  35.              tids.push_back(tid);
  36.          else
  37.              continue;
  38.      }
  39.      for (int i = 0; i < num; i++)
  40.      {
  41.          // 一个一个的等待
  42.          int n = pthread_join(tids[i], nullptr);
  43.          if (n == 0)
  44.          {
  45.              std::cout << "等待新线程成功" << std::endl;
  46.          }
  47.      }
  48.      return 0;
  49. }
复制代码
 监控监控:while :; do ps -aL | head -1 && ps -aL | grep thread; sleep 1; done
注意点: 

(1)
多执行流访问同一份资源,有危险。 
  1. char id[64];多执行流访问同一份资源,有危险
复制代码
很大概率会引起,数据修改没导致结果不精确
(2) 为每一个线程本身创建一个,制止
多执行流访问同一份资源
  1. 多执行流访问同一份资源
复制代码
 
线程ID及历程地址空间布局
  1. • pthread_create函数会产⽣⼀个线程ID,存放在第⼀个参数指向的地址中。
  2. 该线程ID和前⾯说的 线程ID不是⼀回事。
  3. • 前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,
  4. 是操作系统调度器的最⼩单位, 所以需要⼀个数值来唯⼀表⽰该线程。
  5. • pthread_create函数第⼀个参数指向⼀个虚拟内存单元,
  6. 该内存单元的地址即为新创建线程的线 程ID,属于NPTL线程库的范畴。
  7. 线程库的后续操作,就是根据该线程ID来操作线程的。
  8. • 线程库NPTL提供了pthread_self函数,可以获得线程⾃⾝的ID:
复制代码


动态库的链接类比:pthread库的链接 



过程:

磁盘内里的库文件,加载到物理内存内里,在把物理内存内里的库文件映射到mmap动态映射区域,大概可以叫做共享区。
如许历程本身的代码就可以访问到pthread库的内部函数大概数据
在库中对线程的管理: 



线程的概念是在库中维护的,在库内,就肯定会存在多个被创建好的线程,库要不要管理线程呢?要!
先形貌,在构造!!
创建线程形貌符

mmap内里对线程的管理
  1. mmap:动态映射区域
  2. TCB:线程的属性
  3. 线程局部存储:暂时未知
  4. 线程栈:暂时未知
  5. 创建的返回值:
  6. pthread_t tid:线程在库中,对应的管理快的虚拟地址。
  7. 线程函数运行完,返回值,管理快还存在,
  8. 所以线程需要join,
  9. join通过tid拿到线程管理快返回信息后,把管理快释放掉
复制代码
(1)在一个线程中,运行完一个函数,那么,线程就会返回一个void*的值,*ret,但是管理线程的管理快并不会由于函数的实行完而消散,这里就会造成内存走漏,以是我们必要pthread_join,来获取返回值,而且释放管理块的空间
(2)pthread_join工作流程:利用join,就可以得到该线程的返回值,*ret,然后释放管理块的空间,join的参数,tid拿到管理快的起始假造地址,就可以访问该管理快,&ret则用来吸收返回值
(3) join:为什么要用二级指针来担当线程的返回值???便于修改值。单传不可修改值。

一个线程是怎么被创建的??? 


线程的一部门概念一部门在库内里实现:
pthread_create(&tid)创建线程时,起首会在pthread库内里创建一个线程的布局体struct_pthread(线程管理块)然后把id,返回给传入的tid,作为线程在pthread库的假造地址,后序好对线程举行操纵。
一部门在PCB内里实现:
在内核中创建轻量级历程,通过clone来举行实现。(可以在看一遍,做更深了解)
例子:
我叫张三打饭
我是线程,维护信息(线程管理块)
张三给我打饭,根据信息给我打饭,张三是轻量级历程,去给我实行
总结: 

 

ID:线程控制块的起始地址
返回值:线程实行完,的返回值,被join回收
分离:joinable = 0线程分离
joinable!=0,线程不分离
joinable线程标记位
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表