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;
}


目录
相关文章
|
7月前
|
存储 C++
C++语言中指针变量int和取值操作ptr详细说明。
总结起来,在 C++ 中正确理解和运用 int 类型地址及其相关取值、设定等操纵至关重要且基础性强:定义 int 类型 pointer 需加星号;初始化 pointer 需配合 & 取址;读写 pointer 执向之处需配合 * 解引用操纵进行。
628 12
|
存储 C语言 C++
【C++数据结构——栈与队列】顺序栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现顺序栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 1.初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储
744 77
|
存储 C++ 索引
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
【数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】初始化队列、销毁队列、判断队列是否为空、进队列、出队列等。本关任务:编写一个程序实现环形队列的基本运算。(6)出队列序列:yzopq2*(5)依次进队列元素:opq2*(6)出队列序列:bcdef。(2)依次进队列元素:abc。(5)依次进队列元素:def。(2)依次进队列元素:xyz。开始你的任务吧,祝你成功!(4)出队一个元素a。(4)出队一个元素x。
499 13
【C++数据结构——栈与队列】环形队列的基本运算(头歌实践教学平台习题)【合集】
|
存储 IDE Java
java设置栈内存大小
在Java应用中合理设置栈内存大小是确保程序稳定性和性能的重要措施。通过JVM参数 `-Xss`,可以灵活调整栈内存大小,以适应不同的应用场景。本文介绍了设置栈内存大小的方法、应用场景和注意事项,希望能帮助开发者更好地管理Java应用的内存资源。
720 4
|
存储 C语言 C++
【C++数据结构——栈与队列】链栈的基本运算(头歌实践教学平台习题)【合集】
本关任务:编写一个程序实现链栈的基本运算。开始你的任务吧,祝你成功!​ 相关知识 初始化栈 销毁栈 判断栈是否为空 进栈 出栈 取栈顶元素 初始化栈 概念:初始化栈是为栈的使用做准备,包括分配内存空间(如果是动态分配)和设置栈的初始状态。栈有顺序栈和链式栈两种常见形式。对于顺序栈,通常需要定义一个数组来存储栈元素,并设置一个变量来记录栈顶位置;对于链式栈,需要定义节点结构,包含数据域和指针域,同时初始化栈顶指针。 示例(顺序栈): 以下是一个简单的顺序栈初始化示例,假设用C语言实现,栈中存储整数,最大
292 9
|
C++
【C++数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】
【数据结构——栈和队列】括号配对(头歌实践教学平台习题)【合集】(1)遇到左括号:进栈Push()(2)遇到右括号:若栈顶元素为左括号,则出栈Pop();否则返回false。(3)当遍历表达式结束,且栈为空时,则返回true,否则返回false。本关任务:编写一个程序利用栈判断左、右圆括号是否配对。为了完成本关任务,你需要掌握:栈对括号的处理。(1)遇到左括号:进栈Push()开始你的任务吧,祝你成功!测试输入:(()))
364 7
|
存储 算法 Java
Java 内存管理与优化:掌控堆与栈,雕琢高效代码
Java内存管理与优化是提升程序性能的关键。掌握堆与栈的运作机制,学习如何有效管理内存资源,雕琢出更加高效的代码,是每个Java开发者必备的技能。
364 5
|
缓存 安全 C++
C++无锁队列:解锁多线程编程新境界
【10月更文挑战第27天】
1052 7
|
存储
栈内存
栈内存归属于单个线程,也就是每创建一个线程都会分配一块栈内存,而栈中存储的东西只有本线程可见,属于线程私有。 栈的生命周期与线程一致,一旦线程结束,栈内存也就被回收。 栈中存放的内容主要包括:8大基本类型 + 对象的引用 + 实例的方法
174 1
|
消息中间件 存储 安全