C++多线程场景中的变量提前释放导致栈内存异常

简介: C++多线程场景中的变量提前释放导致栈内存异常

多线程场景中的栈内存异常

在子线程中尝试使用当前函数的资源,是非常危险的,但是C++支持这么做。因此C++这么做可能会造成栈内存异常。

正常代码

#include <iostream>
#include <thread>
#include <windows.h>
// 线程函数,用于执行具体的任务
void fun(int param)
{
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
  Sleep(3000);
  param = 1;
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
}
// 线程函数,用于执行具体的任务
void threadFunction() {
  // 在这里执行线程的具体任务
  int num = 2;
  //在函数中还启动了一个线程
  std::thread funThread([num]()
    {
      fun(num);
    });
  funThread.detach();
}
int main() {
  const int numThreads = 3;
  std::thread threads[numThreads];
  // 创建并启动多个线程
  for (int i = 0; i < numThreads; ++i) {
    threads[i] = std::thread(threadFunction);
  }
  //等待所有线程执行完毕
  for (int i = 0; i < numThreads; ++i) {
    threads[i].join();
  }
  Sleep(1000);
  std::cout << "All threads have completed." << std::endl;
  return 0;
}

上述是一个正常的多线程代码。

异常代码

但是如果将其中多线程传参设置为引用传递,可能就会造成栈内存异常了,如下所示:

#include <iostream>
#include <thread>
#include <windows.h>
// 线程函数,用于执行具体的任务
void fun(int& param)//传参修改为引用传递
{
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
  Sleep(3000);
  param = 1;
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
}
// 线程函数,用于执行具体的任务
void threadFunction() {
  // 在这里执行线程的具体任务
  int num = 2;
  //在函数中还启动了一个线程,传参为引用
  std::thread funThread([&num]()
    {
      fun(num);
    });
  funThread.detach();
}
int main() {
  const int numThreads = 3;
  std::thread threads[numThreads];
  // 创建并启动多个线程
  for (int i = 0; i < numThreads; ++i) {
    threads[i] = std::thread(threadFunction);
  }
  //等待所有线程执行完毕
  for (int i = 0; i < numThreads; ++i) {
    threads[i].join();
  }
  Sleep(1000);
  std::cout << "All threads have completed." << std::endl;
  return 0;
}

编译成功,但是运行失败。

运行结果:

上面std::thread funThread(&num{fun(num);});,采用引用传递,并且void fun(int& param)中也采用引用入参的形式。此时可能就会产生内存异常。

因为传入参数num是一个局部参数,我们在fun中修改或读取param时,可能num已经被释放掉了,这时候修改或者读取param就会发生内存错误,其实这里是栈内存异常。这是非常危险的,因为栈内存可能会造成无法估量的问题。本代码中只有这一个作业线程,所以就在这里报错了,所以就在任务函数中崩掉了。但是因为代码中可能还可以读取修改参数param,而导致其他线程中的异常,这种问题往往很难定位。

优化代码

当然我们用引用传递的好处是可以避免临时变量的产生,但变量是复杂对象时,还是可以很大程度减少内存消耗。虽然不能用引用,但是我们可以使用std::move来实现变量所有权的转移,也可以减少临时变量的拷贝。

#include <iostream>
#include <thread>
#include <windows.h>
// 线程函数,用于执行具体的任务
void fun(int&& param)//右值传参
{
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
  Sleep(3000);
  param = 1;
  std::cout << __FUNCTION__ << " param:" << param << std::endl;
}
// 线程函数,用于执行具体的任务
void threadFunction() {
  // 在这里执行线程的具体任务
  int num = 2;
  //在函数中还启动了一个线程
  std::thread funThread(fun, std::move(num));//使用move传入右值
  funThread.detach();
}
int main() {
  const int numThreads = 3;
  std::thread threads[numThreads];
  // 创建并启动多个线程
  for (int i = 0; i < numThreads; ++i) {
    threads[i] = std::thread(threadFunction);
  }
  //等待所有线程执行完毕
  for (int i = 0; i < numThreads; ++i) {
    threads[i].join();
  }
  Sleep(1000);
  std::cout << "All threads have completed." << std::endl;
  return 0;
}


目录
相关文章
|
20天前
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
46 5
|
1月前
|
存储
栈内存
栈内存归属于单个线程,也就是每创建一个线程都会分配一块栈内存,而栈中存储的东西只有本线程可见,属于线程私有。 栈的生命周期与线程一致,一旦线程结束,栈内存也就被回收。 栈中存放的内容主要包括:8大基本类型 + 对象的引用 + 实例的方法
25 1
内存池适用于哪些场景
内存池适用于哪些场景
|
2月前
|
缓存 算法 Java
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
这篇文章详细介绍了Java虚拟机(JVM)中的垃圾回收机制,包括垃圾的定义、垃圾回收算法、堆内存的逻辑分区、对象的内存分配和回收过程,以及不同垃圾回收器的工作原理和参数设置。
79 4
JVM知识体系学习六:JVM垃圾是什么、GC常用垃圾清除算法、堆内存逻辑分区、栈上分配、对象何时进入老年代、有关老年代新生代的两个问题、常见的垃圾回收器、CMS
|
1月前
|
监控 Java 数据库连接
线程池在高并发下如何防止内存泄漏?
线程池在高并发下如何防止内存泄漏?
|
1月前
|
缓存 监控 Java
Java 线程池在高并发场景下有哪些优势和潜在问题?
Java 线程池在高并发场景下有哪些优势和潜在问题?
|
2月前
|
监控 Java 数据库连接
使用线程池时,如何避免内存泄漏的问题?
使用线程池时,如何避免内存泄漏的问题?
|
2月前
|
监控 Java
在实际应用中选择线程异常捕获方法的考量
【10月更文挑战第15天】选择最适合的线程异常捕获方法需要综合考虑多种因素。没有一种方法是绝对最优的,需要根据具体情况进行权衡和选择。在实际应用中,还需要不断地实践和总结经验,以提高异常处理的效果和程序的稳定性。
30 3
|
2月前
|
监控 Java
捕获线程执行异常的多种方法
【10月更文挑战第15天】捕获线程执行异常的方法多种多样,每种方法都有其特点和适用场景。在实际开发中,需要根据具体情况选择合适的方法或结合多种方法来实现全面有效的线程异常捕获。这有助于提高程序的健壮性和稳定性,减少因线程异常带来的潜在风险。
28 1
|
2月前
|
监控 API
Hook 线程与捕获线程执行异常
【10月更文挑战第11天】Hook 线程和捕获线程执行异常是多线程编程中不可或缺的技术。通过深入理解和掌握这些方法,我们可以提高程序的稳定性和可靠性,更好地应对各种异常情况。同时,在实际应用中要注意平衡性能和准确性,制定合理的异常处理策略,以确保程序的正常运行。
35 1