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

文章目录

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

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

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

cpp 复制代码
#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 模式,减少手动清理的复杂性。

cpp 复制代码
#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)

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

c 复制代码
#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;
}

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

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

cpp 复制代码
#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 的状态,如果检测到子线程已停止,则进行清理操作。

c 复制代码
#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这样的机制,所以我们避免在子线程中直接分配资源,而是让主线程负责分配内存,然后将该资源指针传递给子线程使用。

cpp 复制代码
#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;
}
相关推荐
唐诺3 小时前
几种广泛使用的 C++ 编译器
c++·编译器
XH华3 小时前
初识C语言之二维数组(下)
c语言·算法
冷眼看人间恩怨4 小时前
【Qt笔记】QDockWidget控件详解
c++·笔记·qt·qdockwidget
红龙创客4 小时前
某狐畅游24校招-C++开发岗笔试(单选题)
开发语言·c++
Lenyiin4 小时前
第146场双周赛:统计符合条件长度为3的子数组数目、统计异或值为给定值的路径数目、判断网格图能否被切割成块、唯一中间众数子序列 Ⅰ
c++·算法·leetcode·周赛·lenyiin
yuanbenshidiaos6 小时前
c++---------数据类型
java·jvm·c++
十年一梦实验室6 小时前
【C++】sophus : sim_details.hpp 实现了矩阵函数 W、其导数,以及其逆 (十七)
开发语言·c++·线性代数·矩阵
taoyong0016 小时前
代码随想录算法训练营第十一天-239.滑动窗口最大值
c++·算法
这是我586 小时前
C++打小怪游戏
c++·其他·游戏·visual studio·小怪·大型·怪物
Uu_05kkq6 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法