Linux:42线程控制lesson30

代码1:验证join可以去的线程执行完后的退出码/返回值

cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
using namespace std;

void* routine(void* arg){
    string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt--)
    {
        cout<<"我是子线程,名字:"<<name<<endl;
        sleep(1);
    }
    return (void*)10;
}

int main(){
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");
    int cnt = 5;
    while(cnt--){
        cout<<"我是主线程"<<endl;
        sleep(1);
    }
    void* ret = nullptr;
    pthread_join(tid,&ret);
    cout<<"子进程退出,退出码为:"<<(long long)ret<<endl;
}

1.main函数结束,代表主线程结束,也代表进程结束
2.新线程对应的入口函数,运行结束,代表当前线程运行结束。
3.问题:给线程传递的参数和返回值,可以是任意类型

代码2:30min证明::给线程传递的参数和返回值,可以是任意类型 ,下面的例子是类类型

cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include<string>
using namespace std;


class Task{
    public:
        Task(int a,int b)
            :_a(a)
            ,_b(b)
            {}
        int Cal(){
            return _a+_b;
        }
        ~Task() {}
    private:
        int _a;
        int _b;
};
class Result{
    public:
        Result(int result)
            :_result(result)
            {}
        int getresult(){
            return _result;
        }
        ~Result(){}
    private:
        int _result;
};
void* routine(void* arg){
    Task*t = static_cast<Task*>(arg);
    sleep(1);
    Result* r = new Result(t->Cal());
    sleep(1);
    return r;
}

int main(){
    pthread_t tid;
    Task* t = new Task(20,10);
    pthread_create(&tid,nullptr,routine,t);

    Result* ret = nullptr;
    pthread_join(tid,(void**)&ret);
    int n = ret->getresult();
    cout<<"子线程的返回值是:"<<n<<endl;
}

线程终止

如果需要只终⽌某个线程⽽不终⽌整个进程,可以有三种⽅法:

  1. 从线程函数return。这种⽅法对主线程不适⽤,从main函数return相当于调⽤exit。

  2. 线程可以调⽤pthread_exit终⽌⾃⼰。

  3. ⼀个线程可以调⽤pthread_cancel终⽌同⼀进程中的另⼀个线程。

pthread_exit:线程终止

cpp 复制代码
功能:线程终⽌
原型:
 void pthread_exit(void *value_ptr);
 

参数:
 value_ptr:value_ptr不要指向⼀个局部变量,就是指的一个变量
pthread_exit 的参数 value_ptr 是一个指向某个值的指针,该值将作为线程的返回值。这个返回值可以被主线程通过 pthread_join 获取。


返回值:

 ⽆返回值,跟进程⼀样,线程结束的时候⽆法返回到它的调⽤者(⾃⾝)

和return是等价的。

线程等待 :pthread_join

为什么需要线程等待?

• 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。用来释放空间

• 创建新的线程不会复⽤刚才退出线程的地址空间。

cpp 复制代码
功能:等待线程结束
原型

 int pthread_join(pthread_t thread, void **value_ptr);
 

参数:
 thread:线程ID
 value_ptr:它指向⼀个指针,后者指向线程的返回值

 

返回值:成功返回0;失败返回错误码

进程以不同的方式终止,pthread_join得到的线程返回值是不一样的

调⽤该函数的线程将挂起等待,直到id为thread的线程终⽌。thread线程以不同的⽅法终⽌,通过 pthread_join得到的终⽌状态是不同的,总结如下:

cpp 复制代码
1. 如果thread线程通过return返回,
value_ptr所指向的单元⾥存放的是thread线程函数的返回值。

2. 如果thread线程被别的线程调⽤pthread_cancel异常终掉,
value_ptr所指向的单元⾥存放的是常 数PTHREAD_CANCELED。

3. 如果thread线程是⾃⼰调⽤pthread_exit终⽌的,
value_ptr所指向的单元存放的是传给 pthread_exit的参数。

4. 如果对thread线程的终⽌状态不感兴趣,可以传NULL给value_ptr参数。

pthread_cancel:取消线程

参数:tid
主线程取消新线程

取消的时候一定要保证:新线程已经启动
线程被取消,返回的结果是-1

解释:线程如果被取消退出的结果是-1

最推荐的还是return,

线程分离 :pthread_detach(tid)

• 默认情况下,新创建的线程是joinable的,线程退出后,需要对其进⾏pthread_join操作,否则 ⽆法释放资源,从⽽造成系统泄漏。

• 如果不关⼼线程的返回值,join是⼀种负担,这个时候,我们可以告诉系统,当线程退出时,⾃ 动释放线程资源。

如果主线程不想再关心新线程,而是当新线程结束的时候,让他自己释放??

cpp 复制代码
设置新线程为分离状态
技术层面:  线程默认是需要被等待的,joinable。如果不想让主线程等待新线程
想让新线程结束之后,自己退出,设置为分离状态(!joinable or detach)  // todo
理解层面:线程分离,主分离新,新把自己分离。
分离的线程,依旧在进程的地址空间中,进程的所有资源,被分离的线程,依旧可以访问,可以操作。
主不等待新线程。
分离操作
如果线程被设置为分离状态,不需要进行join,join会失败!!

主线程设置分离

cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include <cstring>

using namespace std;

void* routine(void* arg){
    string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt--)
    {
        cout<<"我是子线程,名字:"<<name<<endl;
        sleep(1);
    }
}

int main(){
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");
    //线程分离
    pthread_detach(tid);
    int cnt = 5;
    while(cnt--){
        cout<<"我是主线程"<<endl;
        sleep(1);
    }
    int n = pthread_join(tid,nullptr);
    
    if(n!=0){
        cout<<"pthread_join error"<<endl;
    }else{
        cout<<"pthread_join success"<<endl;
    }
}

一个被分离的进程,调用join会失败。

pthread_join()失败,就会返回0。

线程自己设置分离

cpp 复制代码
#include<iostream>
#include<unistd.h>
#include<pthread.h>
#include <cstring>

using namespace std;

void* routine(void* arg){
    pthread_detach(pthread_self());
    string name = static_cast<const char*>(arg);
    int cnt = 5;
    while(cnt--)
    {
        cout<<"我是子线程,名字:"<<name<<endl;
        sleep(1);
    }
    return nullptr;
}

int main(){
    pthread_t tid;
    pthread_create(&tid,nullptr,routine,(void*)"thread-1");
    
    int cnt = 5;
    while(cnt--){
        cout<<"我是主线程"<<endl;
        sleep(1);
    }
    int n = pthread_join(tid,nullptr);
    
    if(n!=0){
        cout<<"pthread_join error"<<endl;
    }else{
        cout<<"pthread_join success"<<endl;
    }
}

创建多线程

cpp 复制代码
#include <iostream>
#include <string>
#include <vector>
#include <unistd.h>
#include <pthread.h>
#include <cstdio>
#include <cstring>

 创建多线程

 const int num = 10;

 void *routine(void *args)
 {
     sleep(1);
     std::string name = static_cast<const char *>(args);
     delete (char*)args;

     int cnt = 5;
     while (cnt--)
     {
         std::cout << "new线程名字: " << name << std::endl;
         sleep(1);
     }
     return nullptr;
 }

 int main()
 {
     // char id[64];多执行流访问同一份资源,有危险。
     std::vector<pthread_t> tids;
     for (int i = 0; i < num; i++)
     {
         pthread_t tid;
         // bug??
         char *id = new char[64];//需要这么创建
         snprintf(id, 64, "thread-%d", i);
         int n = pthread_create(&tid, nullptr, routine, id);
         if (n == 0)
             tids.push_back(tid);
         else
             continue;
     }

     for (int i = 0; i < num; i++)
     {
         // 一个一个的等待
         int n = pthread_join(tids[i], nullptr);
         if (n == 0)
         {
             std::cout << "等待新线程成功" << std::endl;
         }
     }

     return 0;
 }

监控:while :; do ps -aL | head -1 && ps -aL | grep thread; sleep 1; done

注意点:

(1)多执行流访问同一份资源,有危险。

cpp 复制代码
 char id[64];多执行流访问同一份资源,有危险

很大概率会引起,数据修改没导致结果不准确

(2) 为每一个线程自己创建一个,避免多执行流访问同一份资源

cpp 复制代码
多执行流访问同一份资源

线程ID及进程地址空间布局

cpp 复制代码
• pthread_create函数会产⽣⼀个线程ID,存放在第⼀个参数指向的地址中。
该线程ID和前⾯说的 线程ID不是⼀回事。

• 前⾯讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,
是操作系统调度器的最⼩单位, 所以需要⼀个数值来唯⼀表⽰该线程。

• pthread_create函数第⼀个参数指向⼀个虚拟内存单元,
该内存单元的地址即为新创建线程的线 程ID,属于NPTL线程库的范畴。
线程库的后续操作,就是根据该线程ID来操作线程的。

• 线程库NPTL提供了pthread_self函数,可以获得线程⾃⾝的ID:

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

过程:

磁盘里面的库文件,加载到物理内存里面,在把物理内存里面的库文件映射到mmap动态映射区域,或者可以叫做共享区。

这样进程自己的代码就可以访问到pthread库的内部函数或者数据

在库中对线程的管理:

线程的概念是在库中维护的,在库内,就一定会存在多个被创建好的线程,库要不要管理线程呢?要!

先描述,在组织!!

创建线程描述符

mmap里面对线程的管理

cpp 复制代码
mmap:动态映射区域
TCB:线程的属性
线程局部存储:暂时未知
线程栈:暂时未知
创建的返回值:
pthread_t tid:线程在库中,对应的管理快的虚拟地址。

线程函数运行完,返回值,管理快还存在,
所以线程需要join,
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线程标记位

相关推荐
三体世界5 分钟前
Linux 管道理解
linux·c语言·开发语言·c++·git·vscode·visual studio
茉莉玫瑰花茶39 分钟前
socket编程基础
linux·服务器·网络
LouisCanBe1 小时前
Python 环境管理工具选择与安装实践:Conda 与 uv
linux·python
桦01 小时前
Linux[指令与权限]
linux·运维·服务器
一勺菠萝丶1 小时前
VMware中CentOS 7虚拟机设置固定IP(NAT模式)完整教程
linux·tcp/ip·centos
爬菜1 小时前
rpm包管理
linux
Space-oddity-fang2 小时前
Ubuntu启动SMB(Samba)服务步骤
linux·服务器·github
dustcell.2 小时前
vim 命令复习
linux·编辑器·vim
龙仔7253 小时前
windows使用openssl生成IIS自签证书全流程
linux·运维·服务器