【Linux】线程控制|POSIX线程库|多线程创建|线程终止|等待|线程分离|线程空间布局

news2025/1/12 22:46:50

目录

​编辑

POSIX线程库

 多线程创建

独立栈结构 

获取线程ID 

pthread_self 

 线程终止

return终止线程

pthread_exit

pthread_cancel

线程等待

  退出码问题

 线程分离

测试 

线程ID及地址空间布局 

​编辑


POSIX线程库

pthread线程库是 POSIX线程库的一部分,POSIX线程库也叫原生线程库;

        遵守 POSIX标准:与线程有关的函数构成了一个完整的系列,绝大多数函数的名字都是以“pthread_”开头的
要使用这些函数库,要通过引入头文<pthread.h>
链接这些线程函数库时要使用编译器命令的 “-lpthread” 选项;

成功返回0,失败返回-1

 多线程创建

主线程创建一批线程;

没有在循环创建中添加sleep();

void* handler(void* arg)
{
    const char* name = (const char*)arg;
    while(true)
    {
        cout<<"new thread sucess name:"<<name<<endl;
        sleep(1);
    }
}


int main()
{
    pthread_t tid;
    char namebuff[64];
    for(int i = 0;i<5;i++)  
    {
    //格式化
    snprintf(namebuff,sizeof(namebuff),"%s:%d","thread:",i+1);
    pthread_create(&tid,nullptr,handler,namebuff);  //
    }

    while(true)
    {
        cout << "new thread create success, I am main thread" << endl;
        sleep(1);
    }

    return 0;
}

 观察发现,线程编号不是我们预期的从1.2.3..开始的,而是到了最后一个线程名字;

而且线程确实创建出来了;

添加循环创建时sleep()函数 ;将数组地址改为拷贝

//把参数封成结构体
class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};
 
void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的进行强制类型转换,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "cnt:" << cnt-- << "  &cnt:" << &cnt << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}
 
int main()
{
#define NUM 10
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();//每次循环new的都是一个新对象
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主线程
    while(1)
    {
        cout << "new thread create success, name:main thread" << endl;
        sleep(1);
    }
 
    return 0;
}
  • 主线程创建新线程太快了,新线程都没有机会运行,主线程就把10个新线程创建完毕了,
  • 而传参namebuffer传过去的是 缓冲区namebuffer的起始地址,
  • 第十个线程创建完成之后,缓冲区的内容都被第十个线程的编号内容覆盖了,所以第一次现象线程的编号都是 10

独立栈结构 

线程栈主要用于存储线程的局部变量、函数参数以及调用堆栈。当一个线程开始执行时,它的栈空间会被初始化,并且随着线程的执行,栈空间会被动态地扩展或收缩。

//把参数封成结构体
class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};
 
void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的进行强制类型转换,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "cnt:" << cnt-- << "  &cnt:" << &cnt << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}
 
int main()
{
#define NUM 10
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();//每次循环new的都是一个新对象
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主线程
    while(1)
    {
        cout << "new thread create success, name:main thread" << endl;
        sleep(1);
    }
 
    return 0;
}

在函数内部定义的变量叫局部变量,具有临时性,在多线程的情况下依旧适用,因为每个线程都有自己的独立栈结构 

获取线程ID 

常见获取线程ID的方式有两种:

  • 创建线程时通过输出型参数获得
  • 通过调用pthread_self函数获得

pthread_self 

 

void* start_routine(void* args)
{
    //安全转换
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        std::cout<<name<<" running ...,ID: "<<pthread_self()<<std::endl;
        sleep(1);
    }
}


int main()
{
    pthread_t thread_id;
    
    //创建一个线程
    pthread_create(&thread_id,nullptr,start_routine,(void*)"thread 1:");

    //打印一下主线程的ID
    while(true)
    {
     std::cout<<"main thread"<<" ID: "<<pthread_self()<<std::endl;
        sleep(1);
    }

        // 等待子线程结束
    pthread_join(thread_id, NULL);
    return 0;
}

 

 线程终止

如果需要只终止某个线程而不终止整个进程

可以有三种方法:

  • 从线程函数return。这种方法对主线程不适用,从main函数return相当于调用exit,整个进程退出
  • 线程可以调用 pthread_ exit 终止自己
  • 一个线程可以调用 pthread_ cancel 终止同一进程中的另一个线程

return终止线程

在多线程程序中,return关键字的使用有所不同

当非主线程时,仅表示该线程将终止其执行;在main函数中使用return则意味着整个进程将退出,这会导致进程的所有资源被释放。

用例

主线程创建多个新线程后,休眠2秒,然后进行return,那么整个进程也就退出了  

class ThreadData
{
public:
    pthread_t tid;
    char namebuffer[64];
};
 
void* start_routine(void* args)
{
   ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的进行强制类型转换,C++11
   int cnt = 10;
    while(cnt)
    {
        cout << "new thread create success, name:" << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    delete td;
    return nullptr;
}
 
int main()
{
#define NUM 3
    //创建一批线程
    for(int i = 0; i < NUM; i++)
    {
        ThreadData* td = new ThreadData();
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使线程下标从1开始
        pthread_create(&td->tid, nullptr, start_routine, td);
    }
    //主线程
    cout << "new thread create success, name:main thread" << endl;
    sleep(2);//主线程两秒后退出
    return 0;
}

 如果非主线程执行到return,仅代表该线程结束,线程退出 

pthread_exit

 函数终止线程 

  • exit 是用来终止进程的,任何一个执行流调用 exit,都会使整个进程退出;
  • pthread_exit函数的功能就是终止线程;

 注意

当pthread_exit()和return时,如果返回值是一个指针,该指针指向的内存空间应该是全局的或者malloc分配的(new 本质也是malloc),防止后面有其它线程通过该指针访问出错;

        当然,为了避免潜在威胁,最好确保返回的指针是指向全局,malloc开辟的。这样可以确保即使线程退出,其他线程仍然可以安全地访问这些内存。不能在线程函数的栈上分配,因为当其它线程得到这个返回指针时线程函数已经退出了 ;

pthread_cancel

 函数取消线程 

// 定义一个用于存储线程数据的类
class ThreadData
{
public:
    int number;     // 线程编号
    pthread_t tid;  // 线程ID
    char namebuffer[64]; // 缓冲区,用于存储线程名称
};

// 线程函数
void* start_routine(void* args)
{
    ThreadData* td = static_cast<ThreadData*>(args);
    int cnt = 10;
    while (cnt > 0)
    {
        cout << td->namebuffer << "  cnt:" << cnt-- << endl;
        sleep(1);
    }
    // 使用 pthread_exit 返回线程编号
    pthread_exit((void*)td->number);
}

int main()
{
    vector<ThreadData*> threads; // 用于存储线程数据的向量
#define NUM 5 // 定义要创建的线程数量
    for (int i = 0; i < NUM; i++)     // 创建一批线程
    {
        ThreadData* td = new ThreadData();    // 创建 ThreadData 实例
        td->number = i + 1; // 设置线程编号
        snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);
        pthread_create(&td->tid, nullptr, start_routine, td);
        threads.push_back(td);    // 把每个线程的信息 push 到 threads 向量中
    }
    // 主线程
    for (auto& iter : threads)
    {
        // 输出创建成功的线程信息
        cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
    }
    sleep(5);   // 主线程休眠5秒
    // 取消线程
    for (auto& iter : threads)
    {
        // 取消线程
        pthread_cancel(iter->tid);
        cout << "pthread_cancel: " << iter->namebuffer << endl;
    }
    
    // 等待线程结束
    for (auto& iter : threads)
    {
        void* ret = nullptr;
        int n = pthread_join(iter->tid, &ret);    // 等待线程结束,并获取线程返回值
        // 断言
        assert(n == 0);
        // 输出线程结束的信息和线程退出时返回的值
        cout << "join: " << iter->namebuffer << " success, thread_exit_code: " << (long long)ret << endl;
        delete iter;      // 释放 ThreadData 实例
    }
    // 主线程退出
    cout << "main thread quit" << endl;
    return 0;
}

 

一个线程被取消,它的退出码是 -1

线程等待

线程跟进程一样,创建后也需要主线程等待回收,如果主线程不对新线程进行等待,如果主线程不对新创建的线程进行等待,那么这个新线程的资源将不会被及时回收。这会导致类似“僵尸进程”的问题,也就是内存泄漏 ;

  • 已经退出的线程,其空间没有被释放,仍然在进程的地址空间内。
  • 创建新的线程不会复用刚才退出线程的地址空间

  退出码问题

 

  • void* retval 和 void** retval 有什么关系??
  • 线程函数start_routine函数的返回值类型也是 void*, start_routine函数的返回值返回到哪里??
  • 我们怎么获取线程的退出码,即线程的返回值??

pthread_join函数的参数 void** retval 是一个输出型参数,用来获取线程函数结束时,返回的退出结果
void** retval 是用来获取线程函数返回的退出结果,因为线程函数的返回值是 void*,所以需要用 void** 来接受 void*
注意:线程函数返回的退出结果是返回在线程库当中,参数 void** retval 需要去线程库里面接受才可以返回

 线程分离

新创建的线程是 joinable(可以被等待)的。这意味着线程退出后,需要对其执行 pthread_join 操作来释放资源,否则这些资源将不会被释放,从而可能导致系统资源泄漏。如果不关心线程的返回值的话,线程等待pthread_join是一种负担;
可以使用 pthread_detach来分离线程。分离后的线程会在退出时自动释放其资源,无需主线程进行 pthread_join操作。

 

测试 

// 将线程ID转换为字符串
string changeID(const pthread_t& thread_id)
{
    char tid[128];
                 // 将线程ID格式化为十六进制字符串
    snprintf(tid, sizeof(tid), "0x%x", thread_id);
    return tid;
}

// 线程函数
void* start_routine(void* args)
{
    string threadname = static_cast<const char*>(args);
    int cnt = 5;
    while (cnt > 0)
    {
        // 输出线程名称和线程ID 
        cout << threadname << " running..., threadID:" << changeID(pthread_self()) << endl;
        sleep(1);
        cnt--;
    }
    // 线程函数结束
    return nullptr;
}

int main()
{
    pthread_t tid;
    // 创建一个新线程
    pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
    
    // 分离线程
    pthread_detach(tid); // 分离线程后,线程将在退出时自动释放资源
    
    // 线程默认是 joinable 的,一旦分离,就不允许再使用 pthread_join
    // pthread_join(tid, nullptr); // 这里如果尝试使用 pthread_join 会引发错误
    
    // 获取主线程ID
    string mainID = changeID(pthread_self()); // 主线程ID
    while (1)
    {
        // 输出主线程的信息     由于新线程已经被分离,其ID实际上已经不再有效,只是为了展示。
        cout << "main running..., mainID:" << mainID << ", new threadID:" << changeID(tid) << endl;
        sleep(1);
    }
    return 0;
}

线程ID及地址空间布局 

pthread_create函数会产生一个线程ID,存放在第一个参数指向的地址中。该线程ID和前面说的线程ID不是一回事。
前面讲的线程ID属于进程调度的范畴。因为线程是轻量级进程,是操作系统调度器的最小单位,所以需要一个数值来唯一表示该线程。
pthread_create函数第一个参数指向一个虚拟内存单元,该内存单元的地址即为新创建线程的线程ID,属于NPTL线程库的范畴。线程库的后续操作,就是根据该线程ID来操作线程的。

用户级线程:线程ID值就是库中结构体(TCB)对象的地址 

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

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

相关文章

MySQL运维学习(1):4种日志

1.错误日志 mysql错误日志记录了mysql发生任何严重错误时的信息&#xff0c;若数据库无法正常使用时&#xff0c;可以先查看错误日志 默认情况下错误日志是开启的&#xff0c;文件名为/var/log/mysqld.log&#xff0c;如果文件不在默认位置&#xff0c;可以通过下面的命令查看…

【Java】Junit的使用

Java系列文章目录 补充内容 Windows通过SSH连接Linux 第一章 Linux基本命令的学习与Linux历史 文章目录 Java系列文章目录一、前言二、学习内容&#xff1a;三、问题描述四、解决方案&#xff1a;4.1 Junit测试方法的使用4.2 测试规范 五、总结 一、前言 学习测试JunitMock后…

LLM如何理解图数据? Graph+LLM综述

对图推理&#xff08;RoG&#xff09;&#xff1a;忠实可解释的大语言模型推理方法&#xff08;ICLR2024&#xff09; https://github.com/RManLuo/reasoning-on-graphs 推理图&#xff08;Reasoning on Graphs, RoG&#xff09;提出了一个计划-检索-推理框架&#xff0c;该…

『基础』OS-1计算机系统概述_操作系统发展历程及它的运行环境

操作系统发展历程 常考的三种操作系统对比 批操作系统脱机使用计算机&#xff1b;作业是分批处理的&#xff1b;系统内多道程序并发执行&#xff1b;交互能力差分时操作系统多个用户同时使用计算机&#xff1b;人机交互强&#xff1b;具有每个用户独立使用计算机的独占性&…

学习大数据DAY42 hive 分桶表

目录 分桶表 分桶表注意事项 hive 分桶表-创建分桶表 hive 排序关键字 hive 排序语句 上机练习 分桶表 分区提供一个隔离数据和优化查询的便利方式。不过&#xff0c;并非所有的数据集都可形 成合理的分区。对于一张表或者分区&#xff0c;Hive 可以进一步组织成桶&…

8.21T1 草莓蛋糕(拆max + 权值线段树)

http://cplusoj.com/d/senior/p/NODSX2302A 看到式子&#xff1a; 我们就应该想到拆max 若 我们可以整理推出&#xff1a; 记&#xff1a; 由 L L L 算 C C C&#xff0c;我们满足 h a ≤ h b h_a\le h_b ha​≤hb​&#xff0c;找 c c c 的最小值 C C C 算 L L L 同…

05、Redis实战:优惠券秒杀、全局唯一ID、超买乐观悲观锁、一人一单逻辑、分布式锁、分布式锁的原子性

3、优惠卷秒杀 3.1 -全局唯一ID 每个店铺都可以发布优惠券&#xff1a; 当用户抢购时&#xff0c;就会生成订单并保存到tb_voucher_order这张表中&#xff0c;而订单表如果使用数据库自增ID就存在一些问题&#xff1a; id的规律性太明显受单表数据量的限制 场景分析&#x…

第2章-02-网页中的Document元素

🏆作者简介,黑夜开发者,CSDN领军人物,全栈领域优质创作者✌,CSDN博客专家,阿里云社区专家博主,2023年CSDN全站百大博主。 🏆数年电商行业从业经验,历任核心研发工程师,项目技术负责人。 🏆本文已收录于专栏:Web爬虫入门与实战精讲,后续完整更新内容如下。 文章…

2024思维导图工具评测:性能、易用性全面对比

现在工作日常要处理的数据纷繁复杂&#xff0c;如何高效地组织、理解和记忆这些信息&#xff0c;成为了每个人都需要面对的挑战。不知道你有没有尝试过使用思维导图软件呢&#xff1f;这次我们看看它们是如何帮助我们优化思维、提升效率的。 1.福晰思维导图 链接一下&#xf…

数据结构day04(队列 Queue 循环队列、链式队列)

目录 【1】队列 Queue 1》 队列的定义 2》循环队列 3》链式队列 【1】队列 Queue 1》 队列的定义 队列&#xff08;queue&#xff09;是只允许在一端进行插入操作&#xff0c;而在另一端进行删除操作的线性表。 队列是一种先进先出&#xff08;First In First Out&#xf…

Day23 第十站 文件IO的多路复用

#include <myhead.h>void insert_client(int *client_arr,int *len,int client) {//client_arr[n]{3,4} len&client_count,client_count2;//添加 5 client_arr[2(*len)]5(client)client_arr[*len]client;(*len); } int find_client(int *client_arr,int len,int clie…

Spring DI 数据类型—— set 方法注入

首先新建项目&#xff0c;可参考 初识IDEA、模拟三层--控制层、业务层和数据访问层 一、spring 环境搭建 &#xff08;一&#xff09;pom.xml 导相关坐标 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.or…

代码随想录算法训练营第二十二天| 77. 组合 216.组合总和III 17.电话号码的字母组合

77. 组合 题目&#xff1a; 给定两个整数 n 和 k&#xff0c;返回范围 [1, n] 中所有可能的 k 个数的组合。 你可以按 任何顺序 返回答案。 示例 1&#xff1a; 输入&#xff1a;n 4, k 2 输出&#xff1a; [[2,4],[3,4],[2,3],[1,2],[1,3],[1,4], ] 示例 2&#xff1a…

VAuditDemo安装漏洞

目录 VAuditDemo安装漏洞 index.php header.php config.php lib.php install.php 分析结果 漏洞利用 第一步&#xff1a;删除install.lock文件&#xff0c;访问 install.php 抓包 第二步&#xff1a;通过审计构造payload 第三步&#xff1a;修改抓包请求内容&#x…

客户分级管理系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图详细视频演示为什么选择我官方认证闲鱼玩家&#xff0c;服务很多代码文档&#xff0c;百分百好评&#xff0c;战绩可查&#xff01;&#xff01;入职于互联网大厂&#xff0c;可以交流&#xff0c;共同进步。有保障的售后 代码参考数据库参考源码获取…

《python语言程序设计》2018版第7章第10题设计一个名为time的类,包括hour minute second

#main代码段 def main():a int(time.time())total_second int(a)current_second total_second % 60total_minutes total_second // 60current_minute total_minutes % 60total_hours total_minutes // 60current_hour total_hours % 24b exCode07.Time(current_hour,cu…

SpringBoot中生成二维码的案例实战

❃博主首页 &#xff1a; 「码到三十五」 &#xff0c;同名公众号 :「码到三十五」&#xff0c;wx号 : 「liwu0213」 ☠博主专栏 &#xff1a; <mysql高手> <elasticsearch高手> <源码解读> <java核心> <面试攻关> ♝博主的话 &#xff1a…

基于数据复杂度的数据库选型

数据模型的选择对于 IT 系统的开发至关重要&#xff0c;它不仅决定了数据存储和处理的方式&#xff0c;影响系统的性能、扩展性以及维护性等。本质上来说&#xff0c;不同的数据模型反映了我们对业务问题的不同思考和抽象程度。 今天我们从不同数据模型对于复杂数据和关系的支…

定制化三防平板:为专业领域打造的坚固解决方案

在科技时代&#xff0c;移动设备已经成为各行各业不可或缺的工具。然而&#xff0c;对于一些特殊行业&#xff0c;如军事、野外勘探、物流、医疗和制造业等&#xff0c;普通商用平板往往无法满足其严苛的工作环境需求。三防平板&#xff0c;以其卓越的防护性能和高度的定制化能…

有了这4款工具,你就知道电脑怎么录屏了!

电脑屏幕录屏这个问题很多人都会碰到&#xff0c;比如教学视频录制&#xff0c;游戏技巧分享&#xff0c;软件操作演示等等。因为场景众多&#xff0c;电脑自带的录屏功能不一定能满足&#xff0c;所以借助第三方工具是一个很有效的办法。如果大家不知道如何录屏&#xff0c;可…