C/C++|关于“子线程在堆中创建了资源但在资源未释放的情况下异常退出或挂掉”如何避免?

news2024/11/13 2:51:37

文章目录

  • 主线程监控子线程状态并负责清理资源
  • 使用智能指针(RAII模式)
  • 线程清理处理函数(pthread_cleanup_push、pthread_cleanup_pop)
  • 使用资源管理器或资源吃集中管理资源
  • 通过信号或全局变量监控线程状态
  • 使主线程负责分配和释放资源

在 C/C++ 中处理子线程分配的动态资源因线程异常退出而无法释放的问题,可以采用以下方法。我们将逐条分析并给出示例代码。

主线程监控子线程状态并负责清理资源

使用智能指针管理堆中的资源。

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

#include <iostream>

void* threadFunc(void* arg) {
  int* data = new int(42);
  *(int**)arg = data;  // 将资源的地址传递给主线程
  sleep(1);

  // 模拟子线程崩溃
  pthread_exit(NULL);
  delete data;  // 正常情况下应该释放资源,但是这里不会执行
  return NULL;
}

int main() {
  pthread_t thread;
  int* sharedData = NULL;

  pthread_create(&thread, NULL, threadFunc, &sharedData);

  // 等待子线程完成
  void* status;
  pthread_join(thread, &status);

  // 如果子线程未释放资源,主线程负责清理
  if (sharedData != NULL) {
    std::cout << "Cleaning up memory in main thread: " << *sharedData
              << std::endl;
    delete sharedData;
  }

  return 0;
}

使用智能指针(RAII模式)

这里我们一般是在C++中,当然,在C语言里也可以做类似的封装。

智能指针(如 std::unique_ptr 或 std::shared_ptr)可以自动管理资源生命周期,即使子线程崩溃,也会在对象销毁时释放资源。这种方法利用了 RAII 模式,减少手动清理的复杂性。

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

#include <iostream>
#include <memory>

void* threadFunc(void* arg) {
  std::unique_ptr<int> data(new int(42));  // 使用智能指针分配资源
  std::cout << "Data in thread: " << *data << std::endl;
  sleep(1);

  // 模拟子线程崩溃
  pthread_exit(NULL);
  return nullptr;
}

int main() {
  pthread_t thread;

  pthread_create(&thread, NULL, threadFunc, nullptr);
  pthread_join(thread, nullptr);

  // 完全不需要手动释放资源
  std::cout << "Resource cleanup is handled by unique_ptr." << std::endl;

  return 0;
}

线程清理处理函数(pthread_cleanup_push、pthread_cleanup_pop)

在子线程中注册清理函数,以确保无论线程如何退出(正常或异常),清理函数都会被调用并释放资源。

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

void cleanup(void* arg) {
    free(arg);
    printf("Resource freed in cleanup handler\n");
}

void* threadFunc(void* arg) {
    int* resource = (int*)arg;
    pthread_cleanup_push(cleanup, resource);  // 注册清理函数

    // 使用资源
    *resource = 10;
    printf("Resource used in thread: %d\n", *resource);

    // 模拟异常退出
    pthread_exit(NULL);

    pthread_cleanup_pop(0);  // 0 表示不自动调用清理函数
    return NULL;
}

int main() {
    pthread_t thread;
    int* resource = (int*)malloc(sizeof(int));
    if (resource == NULL) {
        perror("Failed to allocate memory");
        return -1;
    }
    *resource = 5;

    // 创建子线程
    if (pthread_create(&thread, NULL, threadFunc, resource) != 0) {
        perror("Failed to create thread");
        free(resource);  // 如果线程创建失败,释放资源
        return -1;
    }

    // 等待子线程结束
    pthread_join(thread, NULL);

    return 0;
}

使用资源管理器或资源吃集中管理资源

在该方法中,创建一个资源管理器,集中管理所有线程分配的资源。资源管理器记录每个资源的生命周期,并在必要时自动回收。

#include <iostream>
#include <pthread.h>
#include <unordered_map>
#include <mutex>

class ResourceManager {
public:
	void allocateResource(int threadID) {
		std::lock_guard<std::mutex> lock(mutex_);
		resource_[threadID] = new int(42); // 分配资源
	}
private:
	std::unordered_map<int, int*> resource_;
	std::mutex mutex_;
};

ResourceManager manager;

void* threadFunc(void* arg) {
    int threadId = *reinterpret_cast<int*>(arg);
    manager.allocateResource(threadId);    // 请求资源管理器分配资源
    pthread_exit(nullptr);                 // 模拟线程异常退出
    return nullptr;
}

int main() {
    pthread_t thread1, thread2;
    int id1 = 1, id2 = 2;

    pthread_create(&thread1, nullptr, threadFunc, &id1);
    pthread_create(&thread2, nullptr, threadFunc, &id2);

    pthread_join(thread1, nullptr);
    pthread_join(thread2, nullptr);

    // 主线程可以在这里清理
    manager.releaseResource(id1);
    manager.releaseResource(id2);

    return 0;
}

通过信号或全局变量监控线程状态

使用信号或全局变量监控线程状态是实现线程间通信的一种方法。它允许主线程检测子线程的状态(例如,是否正在运行或已结束),并在必要时采取相应措施(如主动释放资源)。为了确保线程间的同步,通常需要借助 互斥锁(std::mutex) 或 原子变量(std::atomic) 来保证数据读写的安全性。
在下面这个示例中,我们定义了一个全局原子变量 thread_running 来表示子线程的状态。主线程会定期检查该变量的状态,并在子线程异常退出时主动释放资源。

在C语言中,我们没有 std::atomic 和 智能指针这样的高级特性,但可以通过 全局变量和互斥锁 来实现类似的功能。
在下面这个示例中,主线程通过一个全局变量 thread_running 来监控子线程的状态。子线程在开始运行时将 thread_running 设置为 1,表示正在运行;当异常退出或结束时,将 thread_running 设置为 0,表示已停止。主线程定期检查 thread_running 的状态,如果检测到子线程已停止,则进行清理操作。

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

int* shared_data = NULL;       // 堆区资源
int thread_running = 0;        // 线程状态标志
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;  // 互斥锁保护资源

void* threadFunc(void* arg) {
    pthread_mutex_lock(&mutex);
    shared_data = (int*)malloc(sizeof(int));   // 分配堆区资源
    if (shared_data == NULL) {
        perror("Failed to allocate memory");
        pthread_mutex_unlock(&mutex);
        pthread_exit(NULL);
    }
    *shared_data = 42;
    thread_running = 1;                         // 标记子线程正在运行
    pthread_mutex_unlock(&mutex);

    printf("Data in thread: %d\n", *shared_data);
    sleep(2);  // 模拟子线程运行时间

    // 模拟子线程异常退出,直接退出而不释放资源
    pthread_mutex_lock(&mutex);
    thread_running = 0;                         // 子线程即将退出
    pthread_mutex_unlock(&mutex);
    pthread_exit(NULL);
}

int main() {
    pthread_t thread;

    // 创建子线程
    if (pthread_create(&thread, NULL, threadFunc, NULL) != 0) {
        perror("Failed to create thread");
        return 1;
    }

    // 主线程监控子线程状态
    while (1) {
        pthread_mutex_lock(&mutex);
        if (thread_running == 0 && shared_data != NULL) {
            // 子线程已退出,但资源未释放,主线程进行清理
            printf("Main thread cleaning up memory: %d\n", *shared_data);
            free(shared_data);
            shared_data = NULL;
        }
        pthread_mutex_unlock(&mutex);

        if (thread_running == 0) {
            break; // 子线程已退出,主线程退出监控循环
        }

        sleep(1); // 定期检查子线程状态
    }

    // 等待子线程退出
    pthread_join(thread, NULL);
    pthread_mutex_destroy(&mutex);
    return 0;
}

使主线程负责分配和释放资源

这里主要是针对C语言中,没有智能指针和RAII这样的机制,所以我们避免在子线程中直接分配资源,而是让主线程负责分配内存,然后将该资源指针传递给子线程使用。

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

void* threadFunc(void* arg) {
	int* shared_resource = (int*)arg;
	*shared_resource = 10;
	pthread_exit(NULL);//模拟异常退出
}

int main () {
	pthread_t thread;
	int* shared_resource = (int*)malloc(sizeof(int));
	if (shared_resource == NULL) {
		perror("Failed to allocate memory");
		return -1;
	}
	*shared_resource = 5;
	if (pthread_create(&thread, NULL, threadFunc, shared_resource) != 0) {
		perror("Failed to create thread");
		free(shared_resource);
		return -1;
	}
	pthread_join(thread, NULL);
	// 主线程负责释放资源
	free(shared_resource);
	return 0;
}

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

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

相关文章

【C++笔记】C++三大特性之继承

【C笔记】C三大特性之继承 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之继承前言一.继承的概念及定义1.1 继承的概念1.2继承的定义1.3继承基类成员访问方式的变化1.4继承类模板 二.基类和派生类间的转…

Colorful/七彩虹iGame G-ONE Plus 12代处理器 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

【LeetCode】分发糖果 解题报告

135. 分发糖果 - 题目链接 n个孩子站成一排。给你一个整数数组ratings表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到1个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果&#xff0c;…

ArcGIS从Excel表格文件导入XY数据并定义坐标系与投影的方法

本文介绍在ArcMap软件中&#xff0c;从Excel表格文件中批量导入坐标点数据&#xff0c;将其保存为.shp矢量格式&#xff0c;并定义坐标系、转为投影坐标系的方法。 已知我们有一个Excel表格文件&#xff08;可以是.xls、.xlsx、.csv等多种不同的表格文件格式&#xff09;&#…

爬虫 - 二手交易电商平台数据采集 (一)

背景: 近期有一个需求需要采集某电商网站平台的商品数据进行分析。因此&#xff0c;我计划先用Python实现一个简单的版本&#xff0c;以快速测试技术的实现可能性&#xff0c;再用PHP实现一个更完整的版本。文章中涉及的技术仅为学习和测试用途&#xff0c;请勿用于商业或非法用…

「C/C++」C++标准库 之 #include<iostream> 标准输入输出

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

AI出图,在家装行业杀疯了!

家装行业作为一个庞大的产业&#xff0c;长期以来面临着诸多难题&#xff0c;而随着AIGC技术的蓬勃发展&#xff0c;AI促进家装设计行业迎来了新的春天。 在传统家装设计流程中&#xff0c;相信大家对“设计环节充满了繁琐与复杂”有着非常深刻的体验&#xff0c;设计师需要花…

MySQL核心业务大表归档过程

记录一下2年前的MySQL大表的归档&#xff0c;当时刚到公司&#xff0c;发现MySQL的业务核心库&#xff0c;超过亿条的有7张表&#xff0c;最大的表有9亿多条&#xff0c;有37张表超过5百万条&#xff0c;部分表行数如下&#xff1a; 在测试的MySQL环境 &#xff1a; pt-archiv…

深度学习——权重初始化、评估指标、梯度消失和梯度爆炸

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;权重初始化零初始化 (Zero Initialization)随机初始化 (Random Initialization)Xavier 初始化&#xff08;Glorot 初始化&#xff09;He 初始化正交初始化&#xff08;Orthogonal Initialization&#xff09;预训练模型…

Proteus中数码管动态扫描显示不全(已解决)

文章目录 前言解决方法后记 前言 我是直接把以前写的 51 数码管程序复制过来的&#xff0c;当时看的郭天祥的视频&#xff0c;先送段选&#xff0c;消隐后送位选&#xff0c;最后来个 1ms 的延时。 代码在 Proteus 中数码管静态是可以的&#xff0c;动态显示出了问题——显示…

简单说一下Pinia 和 Vuex的区别

Pinia 和 Vuex 是 Vue.js 生态系统中两种状态管理库&#xff0c;它们都用于管理复杂应用的状态。尽管它们的目标相似&#xff0c;但在设计和使用上有许多不同之处。以下是 Pinia 和 Vuex 的主要区别&#xff1a; 1. 设计理念 Vuex 集中式存储&#xff1a;Vuex 采用单一的集中…

个人博客静态样式部署

首页部分 views/layout/layoutwave.vue <script setup> import router from /router; import { ref, onMounted, onUnmounted } from vue import /assets/img/icons/font_p81061dps7l/iconfont.cssconst canvas ref(null) const canvasFa ref(null) const fish ref(n…

综合文化信息管理系统|基于java和小程序的综合文化信息管理系统设计与实现(源码+数据库+文档)

综合文化信息管理系统 目录 基于java和小程序的打印室预约系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&…

渗透测试(socket,namp,scapy)

socket:可以用来实现不同虚拟机或者不同计算机之间的通信。 socket常用函数&#xff1a; sock.bind(host,port) //host可接受client范围&#xff0c;以及连接的端口 sock.listen()//sever开启监听连接 sock.accpet()//返回 sock&#xff0c;addr 用来接受和发送数据 addr…

leetcode刷题记录(二十六)——151. 反转字符串中的单词

&#xff08;一&#xff09;问题描述 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/reverse-words-in-a-string/desc…

python中常见的8种数据结构之一数组的应用

在Python中&#xff0c;数组是一种常见的数据结构&#xff0c;用于存储一系列相同类型的元素。在实际应用中&#xff0c;数组可以用于解决各种问题。 以下是数组在Python中的一些常见应用&#xff1a; 1. 存储和访问数据&#xff1a;数组可以用于存储和访问一组数据。可以通过…

JS禁用鼠标滚动条功能且滚动条不消失教程

这个JS功能我找了好久好久才找到的&#xff0c;希望能够帮助到大家&#xff0c;网上有很多教程虽然能够实现禁用滚动条的效果&#xff0c;但是滚动条却直接消失不见了&#xff0c;那么今天我就把禁用滚动条但滚动条不消失的JS代码分享给大家。 实例代码如下&#xff1a; JS禁用…

学习虚幻C++开发日志——定时器

官方文档&#xff1a;虚幻引擎中的Gameplay定时器 | 虚幻引擎 5.5 文档 | Epic Developer Community | Epic Developer Community 定时器 安排在经过一定延迟或一段时间结束后要执行的操作。例如&#xff0c;您可能希望玩家在获取某个能力提升道具后变得无懈可击&#xff0c;…

记录Postman接口测试,配置token为全局变量,配置测试环境

软件测试资料领取&#xff1a;[内部资源] 想拿年薪40W的软件测试人员&#xff0c;这份资料必须领取~ 软件测试面试刷题工具&#xff1a;软件测试面试刷题【800道面试题答案免费刷】 为什么要进行接口测试&#xff1a; 因为不同端&#xff08;前段&#xff0c;后端&#xff0…

单臂路由技术,eNSP实验讲解

单臂路由技术&#xff0c;eNSP实验讲解 一、简要介绍1、概念2、工作原理3、优点4、缺点5、应用场景举例 二、eNSP仿真实验1、步骤一&#xff1a;2、步骤二&#xff1a;3、步骤三&#xff1a;4、步骤四&#xff1a; 三、总结。 一、简要介绍 1、概念 单臂路由&#xff08;Rout…