线程的pthread_create、pthread_join、pthread_exit、pthread_detach函数

news2025/3/25 12:07:12

线程的创建(pthread_create)

pthread_t tid;//本质是unsigned long类型,打印时得到的是该线程的虚拟地址

int pthread_create(
    pthread_t *thread, 
    const pthread_attr_t *attr,
    void *(*start_routine)(void*), 
    void *arg
);
  • pthread_t *thread:存储新线程的标识符(ID) 
  • const pthread_attr_t *attr:线程属性(如栈大小等)一般为 nullptr 使用默认属性
  • void *(*start_routine)(void*):指向线程要执行的函数(返回值和参数类型都要是void*)
  • void *arg:传递给线程调用的函数的参数(可以是字符串、整型、类对象等)
  • 返回值:线程的所有相关函数执行成功时均返回0,失败时均返回错误码 
#include <iostream>
#include <string>
#include <cstring>
#include <pthread.h>
#include <unistd.h>

class threadData {
public:
    std::string name;
    int num;
};

// 线程函数:传递字符串
void* threadrun1(void* arg) {
    const char* name = static_cast<const char*>(arg);
    int cnt = 10;
    while (cnt--) {
        std::cout << "name = " << name << ", cnt = " << cnt << std::endl;
        sleep(1);
    }
    return nullptr;
}

// 线程函数:传递整型
void* threadrun2(void* arg) {
    int num = *static_cast<int*>(arg);
    int cnt = 10;
    while (cnt--) {
        std::cout << "num = " << num << ", cnt = " << cnt << std::endl;
        sleep(1);
    }
    return nullptr;
}

// 线程函数:传递类对象(堆分配)
void* threadrun3(void* arg) {
    threadData* data = static_cast<threadData*>(arg);
    int cnt = data->num;
    while (cnt--) {
        std::cout << "[ClassThread] Name: " << data->name 
                  << ", Count: " << cnt << std::endl;
        sleep(1);
    }
    delete data;  // 释放堆内存
    return nullptr;
}

int main() {
    pthread_t tid1, tid2, tid3;
    
    // 动态分配类对象(堆内存)
    threadData* myData = new threadData();  // 使用 new 分配
    myData->name = "ClassThread";
    myData->num = 8;

    // 创建线程
    int n1 = pthread_create(&tid1, nullptr, threadrun1, (void*)"thread 1");
    int num_arg = 42;
    int n2 = pthread_create(&tid2, nullptr, threadrun2, (void*)&num_arg);
    int n3 = pthread_create(&tid3, nullptr, threadrun3, (void*)myData);  // 传递堆对象指针

    // 错误检查
    if (n1 || n2 || n3) {
        int error_code = n1 ? n1 : (n2 ? n2 : n3);
        std::cerr << "线程创建失败: " << strerror(error_code) << std::endl;
        delete myData;  // 错误时释放堆内存
        return 1;
    }

    // 等待线程结束
    pthread_join(tid1, nullptr);
    pthread_join(tid2, nullptr);
    pthread_join(tid3, nullptr);

    // 注意:myData 已在 threadrun3 中被释放,此处无需重复 delete
    std::cout << "\n所有线程执行完毕,主线程退出" << std::endl;
    return 0;
}

注意事项:多线程编程中,​虽然每个线程有独立的栈空间,但主线程传递给子线程的类对象或结构体对象仍应通过 new 在堆上动态分配:

  1. 虽然栈空间独立,但整个进程的虚拟地址空间是共享的,因此线程间可通过指针访问任意内存地址(包括其他线程的栈)
  2. 主线程如果以pthread_exit的方式退出,那么主线程栈上的对象会被销毁,此时子线程再访问已销毁的栈对象会导致悬空指针问题,而如果是堆对象的话只要子线程运行期间该对象未被释放,就可以避免悬垂指针(若主线程不以pthread_exit的方式退出时所有线程都退出就不会出现悬空指针问题

线程的等待(pthread_join)

// 线程执行的函数:传递类对象(堆分配)
void* threadrun3(void* arg) {
    threadData* data = static_cast<threadData*>(arg);
    int cnt = data->num;
    while (cnt--) {
        std::cout << "[ClassThread] Name: " << data->name 
                  << ", Count: " << cnt << std::endl;
        sleep(1);
    }
    delete data;  // 释放堆内存
    return (void*)111;//返回线程执行完该函数后的信息
}           |
            |    //返回信息放入ret中
            V
        void* ret = nullptr;//ret是指针变量,被分配了空间,使用pthread_join时都要有一个ret这种的一级指针存放返回信息
            |_____________________________
                                         |
                                         V
int pthread_join(pthread_t thread, void ​**retval);//pthread_join函数的原始状态
                               ||
                 int pthread_join(tid,&ret);//实际中会写成
  • pthread_t thread:目标线程的标识符pthread_t 类型)
  • void ​**retval:输出型参数,二级指针,用于存储目标线程的退出状态(可为 NULL
  • ret的类型不一定是void*可根据线程函数的返回值决定,但是pthread_join的第二个参数的类型必须是void**,如果不是需要强转
  • 若多个线程尝试 pthread_join 同一个线程,会引发未定义行为

线程函数的返回值 

基本概念:线程的返回值可以是整型、字符串也可以是对象,且必须提供void*类型的返回值

#include <iostream>
#include <string>
#include <cstring>
#include <pthread.h>
#include <unistd.h>

//线程执行函数
class threadData {
public:
    int Task()
    {
        int result = x + y;
        return result;
    }
  int x;
  int y;
  int result;
};

//线程结果查询函数
class threadResult
{
public:
    std::string print()
    {
        return std::to_string(x) + "+" + std::to_string(y) + " = " + std::to_string(result);//这里的+表示追加字符串
    }
public:
    int result;
    int x;
    int y; 
};


// 线程函数:传递类对象(堆分配)
void* threadrun(void* arg) {
    threadData* data = static_cast<threadData*>(arg);
    threadResult* result = new threadResult();
    int cnt = 3;
    while(cnt)
    {
        sleep(3);
        std::cout <<" run ... , cnt: " << cnt-- <<std::endl;
        result->result = data->Task();
        result->x = data->x;
        result->y = data->y;
    }

    delete data;  // 释放堆内存
    return (void*)result;
}

int main() {
    pthread_t tid;
    
    // 动态分配类对象(堆内存)
    threadData* myData = new threadData();  // 使用 new 分配
    myData->x = 1;
    myData->y = 2;

    // 创建线程
    int n = pthread_create(&tid, nullptr, threadrun, (void*)myData);  // 传递堆对象指针

    // 错误检查
    if (n != 0) {
        std::cerr << "线程创建失败: " << strerror(n) << std::endl;
        delete myData;  // 错误时释放堆内存
        return 1;
    }

    // 等待线程结束
    threadResult* result = nullptr;

    int m = pthread_join(tid, (void**)&result);
    if (m == 0 && result != nullptr) {
        std::cout << "子线程结果: " << result->print() << std::endl;
        delete result;  // 主线程负责释放结果
    } else {
        std::cerr << "线程执行失败或未返回结果" << std::endl;
    }

    return 0;
}

多线程的创建

#include <iostream>
#include <vector>
#include <unistd.h>

//打印线程地址
std::string PrintToHex(pthread_t &tid)
{
    char buffer[64];
    snprintf(buffer,sizeof(buffer),"0x%lx",tid);//打印无符号长整型的
    return buffer;
}

void* ThreadRun(void* args)
{
    std::string name = static_cast<const char*>(args);
    while(true)
    {
        std::cout <<name << "is running " <<std::endl;
        sleep(1);
        break;
    }
    return args;//返回线程名
}

const int num = 10;

int main()
{
    std::vector<pthread_t> tids;
    //创建多线程
    for(int i = 0;i<num;i++)
    {
        //1、线程的id
        pthread_t tid;
        //2、线程的名字
        char* name = new char[128];//每个名字的长度(缓冲区)
        snprintf(name,128,"thread-%d ",i+1);//格式化向name指向的缓冲区中打印
        pthread_create(&tid,nullptr,ThreadRun,name /*线程的名字*/);
        //3、保存所有线程的id消息
        tids.emplace_back(tid);//使用emplace_back而不是push_back向数组中插入,避免了临时对象的出现
    }   

    //主线程等待所有线程跑完
    for(auto tid : tids)
    {
        void* name = nullptr;//获取线程的返回信息
        pthread_join(tid,&name);
        //std::cout << PrintToHex(tid) <<" quit... " <<std::endl;
        std::cout << (const char*)name << " quit..." <<std::endl;
        delete (const char*)name;
    }
    return 0;
}
┌───────────────────────┐
│      主线程流程         │
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│ 创建线程ID容器 vector  │
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│  循环创建10个子线程     │
│ ┌─────────┐           │
│ │i=0~9    │           │
│ ├────┬────┤           │
│ │分配name│            │
│ │内存(new char[128])│
│ ├────┼────┤           │
│ │格式化线程名          │
│ │snprintf(thread-X)  │
│ ├────┼────┤           │
│ │创建线程             │
│ │pthread_create()    │
│ ├────┼────┤           │
│ │保存tid到容器        │
│ │tids.emplace_back() │
│ └────┴────┘           │
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│  等待所有子线程完成     │
│ ┌─────────┐           │
│ │遍历tids容器│         │
│ ├────┬────┤           │
│ │等待线程 │           │
│ │pthread_join()       │
│ ├────┼────┤           │
│ │获取返回指针          │
│ │void* name          │
│ ├────┼────┤           │
│ │打印退出信息          │
│ │cout << name        │
│ ├────┼────┤           │
│ │释放内存             │
│ │delete[] name       │
│ └────┴────┘           │
└──────────┬────────────┘
           │
           ▼
┌───────────────────────┐
│      主线程退出         │
└───────────────────────┘

 

线程的终止与取消(pthread_exit、pthread_cancel)

//调用此函数后,当前线程终止,retval 会被传递给 pthread_join 的接收参数(类似于线程调用函数的返回值)
void pthread_exit(void *retval);


int pthread_cancel(pthread_t thread);//不常用
  • void *retval线程退出时返回的指针
  • pthread_t thread:目标线程的标识符

注意事项:

1、主线程最好最后一个结束,防止主线程结束后导致的所有线程退出的问题

2、子线程的退出标志是其所调用的函数return了

3、exit()用于终止进程,如果在多线程时调用exit()会导致当前进程结束,所有主线程退出

4、pthread_exit()用于终止线程 == 子线程调用函数中的return

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void* thread_func(void* arg) {
    int* result = malloc(sizeof(int));
    *result = 42;
    pthread_exit(result); // 返回堆内存指针(充当函数的返回值)
}

int main() {
    pthread_t tid;
    pthread_create(&tid, nullptr, thread_func, NULL);
    
    void* retval;
    pthread_join(tid, &retval);
    printf("Result: %d\n", *(int*)retval); // 输出 42
    free(retval); // 必须手动释放
}

5、主线程使用pthread_exit退出后,子线程还可以继续运行,但退出位置的后续代码不能执行

#include <iostream>
#include <pthread.h>
#include <unistd.h>

void*  thread_func(void* args)
{...} <——————————————————————————————————————————————————
                                                        |
int main() {                                            |
    pthread_t tid;                                      |
    pthread_create(&tid, NULL, thread_func, NULL);      |
    pthread_exit(NULL); // 主线程退出但子线程继续运行; ————     
    // 后续代码不会执行
}

6、 pthread_cancel退出线程时,该线程的返回信息是-1

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

void* thread_func(void* arg) {
    while (true) {
        printf("线程运行中...\n");
        sleep(1);  // 取消点(sleep 是取消点函数)
    }
    return NULL;
}

int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, thread_func, NULL);
    sleep(2);  // 等待线程运行

    // 发送取消请求
    pthread_cancel(tid);

    void* retval;
    pthread_join(tid, &retval);

    // 检查返回值
    if (retval == PTHREAD_CANCELED) {
        printf("线程被取消!返回值地址: %p\n", retval);
    } else {
        printf("线程正常退出,返回值: %p\n", retval);
    }
    return 0;
}

线程的分离(pthread_detach

int pthread_detach(pthread_t thread);
  • pthread_t thread:目标线程的标识符

适用场景:当线程无需返回数据,且主线程无需等待其结束时

功能:被分离线程终止后,系统会自动回收其栈和线程描述符,避免资源泄漏和僵尸线程的出现

注意事项:

1、若线程已经调用pthread_detach,则不能再调用pthread_join获取该线程的返回信息,否则线程退出并报错

2、若不分离且未调用 pthread_join,线程终止后仍占用资源,成为“僵尸线程”

3、主线程无需等待被分离的线程结束,且子线程调用的函数中传入pthread_self()可自行分离,且常常需要资源安全验证和同步机制

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>

// 线程数据结构体(需动态分配)
typedef struct {
    int id;
    char* task_name;
} ThreadData;

// 资源清理函数
void cleanup(void* arg) {
    ThreadData* data = (ThreadData*)arg;
    printf("[清理] 释放资源: Task%d (%s)\n", data->id, data->task_name);
    free(data->task_name);
    free(data);
}

// 线程函数:子线程自行分离 + 资源安全验证
void* thread_func(void* arg) {
    // 注册清理函数(确保资源释放)
    pthread_cleanup_push(cleanup, arg);

    // 尝试自行分离(防御性编程)
    int detach_ret = pthread_detach(pthread_self());
    if (detach_ret != 0) {
        fprintf(stderr, "[子线程] 分离失败: %s\n", strerror(detach_ret));
    }

    ThreadData* data = (ThreadData*)arg;
    printf("[子线程] Task%d 执行: %s\n", data->id, data->task_name);
    sleep(2);  // 模拟耗时操作

    // 正常退出时手动触发清理(参数0表示不执行清理)
    pthread_cleanup_pop(0);
    return NULL;
}

int main() {
    pthread_t tid;
    
    // 动态分配线程数据(需确保资源安全)
    ThreadData* data = malloc(sizeof(ThreadData));
    data->id = 1;
    data->task_name = strdup("ProcessData");

    // 创建线程
    int create_ret = pthread_create(&tid, NULL, thread_func, data);
    if (create_ret != 0) {
        fprintf(stderr, "[主线程] 创建失败: %s\n", strerror(create_ret));
        free(data->task_name);
        free(data);
        return 1;
    }

    // 主线程尝试分离子线程(双重保障)
    int detach_ret = pthread_detach(tid);
    if (detach_ret != 0) {
        fprintf(stderr, "[主线程] 分离失败: %s\n", strerror(detach_ret));
    }

    printf("[主线程] 继续运行,不等待子线程\n");
    
    // 等待足够时间确保子线程完成(实际项目需条件变量同步)
    sleep(3);  // 必须大于子线程的 sleep(2)
    return 0;
}

4、pthread_detach的返回值有多种不同的错误码

int ret = pthread_detach(tid);
if (ret == EINVAL) {
    fprintf(stderr, "线程已分离或不存在\n");
} else if (ret == ESRCH) {
    fprintf(stderr, "线程ID无效\n");
}

~over~

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

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

相关文章

测试专项4:AI算法测试在测试行业中,该如何定位自己自述

这岗位到底干啥的&#xff1f; 打个比方&#xff1a; 你就像AI模型的“质检员产品经理风险顾问”三合一。 质检员&#xff1a; 别人造了个AI模型&#xff08;比如人脸识别系统&#xff09;&#xff0c;你不能光看它实验室成绩好&#xff0c;得把它丢到现实里折腾&#xff1a;…

【C语言系列】数据在内存中存储

数据在内存中存储 一、整数在内存中的存储二、大小端字节序和字节序判断2.1什么是大小端&#xff1f;2.2练习2.2.1练习12.2.2练习22.2.3练习32.2.4练习42.2.5练习52.2.6练习6 三、浮点数在内存中的存储3.1练习3.2浮点数的存储3.2.1 浮点数存的过程3.2.2 浮点数取的过程 3.3题目…

【中文翻译】第12章-The Algorithmic Foundations of Differential Privacy

由于GitHub项目仅翻译到前5章&#xff0c;我们从第6章开始通过大语言模型翻译&#xff0c;并导出markdown格式。 大模型难免存在错漏&#xff0c;请读者指正。 教材原文地址&#xff1a;https://www.cis.upenn.edu/~aaroth/Papers/privacybook.pdf 12 其他模型 到目前为止&…

图解模糊推理过程(超详细步骤)

我们前面已经讨论了三角形、梯形、高斯型、S型、Z型、Π型6种隶属函数&#xff0c;下一步进入模糊推理阶段。 有关六种隶属函数的特点在“Pi型隶属函数&#xff08;Π-shaped Membership Function&#xff09;的详细介绍及python示例”都有详细讲解&#xff1a;https://lzm07.b…

datawhale组队学习-大语言模型-task5:主流模型架构及新型架构

目录 5.3 主流架构 5.3.1 编码器-解码器架构 5.3.2 因果解码器架构 5.3.3 前缀解码器架构 5.4 长上下文模型 5.4.1 扩展位置编码 5.4.2 调整上下文窗口 5.4.3 长文本数据 5.5 新型模型架构 5.5.1 参数化状态空间模型 5.5.2 状态空间模型变种 5.3 主流架构 在预训…

RAG 架构地基工程-Retrieval 模块的系统设计分享

目录 一、知识注入的关键前奏——RAG 系统中的检索综述 &#xff08;一&#xff09;模块定位&#xff1a;连接语言模型与知识世界的桥梁 &#xff08;二&#xff09;核心任务&#xff1a;四大关键问题的协调解法 &#xff08;三&#xff09;系统特征&#xff1a;性能、精度…

(C语言)习题练习 sizeof 和 strlen

sizeof 上习题&#xff0c;不知道大家发现与上一张的习题在哪里不一样嘛&#xff1f; int main() {char arr[] "abcdef";printf("%zd\n", sizeof(arr));printf("%zd\n", sizeof(arr 0));printf("%zd\n", sizeof(*arr));printf(&…

Unity Animation的其中一种运用方式

Animation是Unity的旧的动画系统&#xff0c;先说目的&#xff0c;其使用是为了在UI中播放动效&#xff0c;并且在动效播放结束后接自定义事件而设计的 设计的关键点在于&#xff0c;这个脚本不是通过Animation直接播放动画片段&#xff0c;而是通过修改AnimationState的nor…

框架的CVE漏洞利用 php类 java类 手工操作和自动化操作蓝队分析漏洞利用的流量特征

前言 php重要框架和基本的识别特征 php的主要是 tp框架 和 laravel 当然还有 yii等 tp的主要特征 1\报错信息&#xff1a; 2、图标 3、请求头 Laravel特征 1、报错信息 2、请求头 php框架CVE利用 lavarvel 工具 https://github.com/zhzyker/CVE-2021-3129 https://git…

【算法day19】括号生成——数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

括号生成 https://leetcode.cn/problems/generate-parentheses/description/ 数字 n 代表生成括号的对数&#xff0c;请你设计一个函数&#xff0c;用于能够生成所有可能的并且 有效的 括号组合。 左括号数必须大于右括号数&#xff0c;且小于等于n class Solution { publ…

Qt5.15.2实现Qt for WebAssembly与示例

目录 1.什么是Qt for WebAssembly&#xff1f; 1.1 什么是 WebAssembly&#xff1f; 1.2 WebAssembly 的优势 1.3 什么是 Qt for WebAssembly&#xff1f; 1.4 Qt for WebAssembly 的特点 1.5 编译过程 1.6 运行时环境 注意&#xff01;&#xff01;&#xff01;注意&am…

好吧好吧,看一下达梦的模式与用户的关系

单凭个人感觉&#xff0c;模式在达梦中属于逻辑对象合集&#xff0c;回头再看资料 应该是一个用户可以对应多个模式 问题来了&#xff0c;模式的ID和用户的ID一样吗&#xff1f; 不一样 SELECT USER_ID,USERNAME FROM DBA_USERS WHERE USERNAMETEST1; SELECT ID AS SCHID, NA…

HOW - DP 动态规划系列(三)(含01背包问题)

目录 一、01背包问题最直接的暴力解法动态规划解法 二、完全背包 通过几个算法的学习&#xff0c;理解和掌握动态规划来解决背包问题。 一、01背包问题 对于面试的话&#xff0c;掌握01背包和完全背包就够用了&#xff0c;最多可以再来一个多重背包。 如果这几种背包分不清&…

在linux服务器部署Heygem

前言&#xff1a; Heygem官方文档上提供了基于windwos系统的安装方案。在实际使用过程中个人电脑的配置可能不够。这个时候如果服务器配置够的话&#xff0c;可以尝试在服务器上装一下。但是服务器一般都是linux系统的&#xff0c;于是这篇教程就出现了… 可行性分析 通读安装…

图书管理系统系统-Java、SpringBoot、Vue和MySQL开发的图书馆管理系统

「springboot、vue图书馆管理系统.zip」 链接&#xff1a;https://pan.quark.cn/s/5a929a7e9450 分享一个图书管理系统&#xff0c;Java、SpringBoot、Vue和MySQL开发的图书馆管理系统 以下是对文本内容的总结&#xff1a; 项目概述 项目名称与背景&#xff1a; 项目概述 项…

[c语言日寄]数据输入

【作者主页】siy2333 【专栏介绍】⌈c语言日寄⌋&#xff1a;这是一个专注于C语言刷题的专栏&#xff0c;精选题目&#xff0c;搭配详细题解、拓展算法。从基础语法到复杂算法&#xff0c;题目涉及的知识点全面覆盖&#xff0c;助力你系统提升。无论你是初学者&#xff0c;还是…

字节DAPO算法:改进DeepSeek的GRPO算法-解锁大规模LLM强化学习的新篇章(代码实现)

DAPO算法&#xff1a;解锁大规模LLM强化学习的新篇章 近年来&#xff0c;大规模语言模型&#xff08;LLM&#xff09;在推理任务上的表现令人瞩目&#xff0c;尤其是在数学竞赛&#xff08;如AIME&#xff09;和编程任务中&#xff0c;强化学习&#xff08;RL&#xff09;成为…

计算机操作系统(四) 操作系统的结构与系统调用

计算机操作系统&#xff08;四&#xff09; 操作系统的结构与系统调用 前言一、操作系统的结构1.1 简单结构1.2 模块化结构1.3 分层化结构1.4 微内核结构1.5 外核结构 二、系统调用1.1 系统调用的基本概念1.2 系统调用的类型 总结&#xff08;核心概念速记&#xff09;&#xf…

DeepSeek技术架构解析:MoE混合专家模型

一、前言 2025年初&#xff0c;DeepSeek V3以557万美元的研发成本&#xff08;仅为GPT-4的1/14&#xff09;和开源模型第一的排名&#xff0c;在全球AI领域掀起波澜。其核心创新之一——混合专家模型&#xff08;Mixture of Experts, MoE&#xff09;的优化设计&#xff0c;不…

【正点原子】AI人工智能深度学习(RV1126/RK3568/RK3588)-第1期 准备篇

1.1SDK编译后的目录 1、真正的根文件系统镜像存放目录 2、非必须&#xff0c;负责系统升级等&#xff0c;kerneldtbramdisk组成的根文件系统 1.2文件系统分区 1.3开机自启动 1.6设置静态ip地址 1.8RKMedia框架/编译测试SDK自带RKMedia例程 出厂系统以下内容都是默认…