- VisualStudio2022插件的安装及使用-编程手把手系列文章
- pprof-在现网场景怎么用
- C#实现的下拉多选框,下拉多选树,多级节点
- 【学习笔记】基础数据结构:猫树
今天复习前几年在项目过程中积累的各类技术案例,有一个小的 coredump 案例,当时小组里几位较资深的同事都没看出来,后面是我周末查了两三个小时解决掉的,今天再做一次系统的总结,给出一个复现的案例代码,案例代码比较简单,便于学习理解.
原则:临时对象不应该被 lambda 引用捕获,因为临时对象在它所在的语句结束就会被析构掉,只能采用值捕获。 当临时对象比较隐蔽时,我们就可能犯这个低级错误。本文介绍一类case:以基类智能指针对象的 const 引用为函数形参,并在函数内对该参数做引用捕获,然后进行跨线程异步使用。当函数调用者使用派生类智能指针作为实参时,此时派生类智能指针对象会向上转换为基类智能指针对象,这个转换是隐式的,产生的对象是临时对象,然后被 lambda 引用捕获,后续跨线程使用引发“野引用” core.
下面写一个简单的 demo 代码来模拟这个案例。案例涉及的代码流程,如下图所示: 其中,基类 BaseTask,派生类 DerivedTask,main 函数将 lambda 闭包抛到工作线程中异步执行。 详细示例代码如下:
/**
* @brief 关键字:lambda、多线程、std::shared_ptr 隐式向上转换
* g++ main.cc -std=c++17 -O3 -lpthread
*/
#include <atomic>
#include <chrono>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
using namespace std::chrono_literals;
/// 简易线程池
template <typename Func>
class ThreadPool {
public:
~ThreadPool() {
stop_ = true;
for (auto& item : workers_) {
item.join();
}
}
void Run() {
static constexpr uint32_t kThreadNum = 2;
uint32_t idx = 0;
for (uint32_t idx = 0; idx != kThreadNum; ++idx) {
workers_.emplace_back(std::thread([this, idx] { ThreadFunc(idx); }));
mutexs_.emplace_back(std::make_shared<std::mutex>());
}
job_queues_.resize(kThreadNum);
}
void Stop() { stop_ = true; }
bool Post(Func&& f) {
if (!stop_) {
uint32_t index = ++job_cnt_ % job_queues_.size();
auto& queue = job_queues_[index];
std::lock_guard<std::mutex> locker(*mutexs_[index]);
queue.push(std::move(f));
return true;
}
return false;
}
void ThreadFunc(uint32_t idx) {
auto& queue = job_queues_[idx];
auto& mutex = *mutexs_[idx];
// 退出前清空任务队列
while (true) {
if (!queue.empty()) {
std::lock_guard<std::mutex> locker(mutex);
const auto& job_func = queue.front();
job_func();
queue.pop();
} else if (!stop_) {
std::this_thread::sleep_for(10ms);
} else {
break;
}
}
}
private:
/// 工作线程池
std::vector<std::thread> workers_;
/// 任务队列,每个工作线程一个队列
std::vector<std::queue<Func>> job_queues_;
/// 任务队列的读写保护锁,每个工作线程一个锁
std::vector<std::shared_ptr<std::mutex>> mutexs_;
/// 是否停止工作
bool stop_ = false;
/// 任务计数,用于将任务均衡分配给多线程队列
std::atomic<uint32_t> job_cnt_ = 0;
};
using MyThreadPool = ThreadPool<std::function<void()>>;
/// 基类task
class BaseTask {
public:
virtual ~BaseTask() = default;
virtual void DoSomething() = 0;
};
using BaseTaskPtr = std::shared_ptr<BaseTask>;
/// 派生task
class DeriveTask : public BaseTask {
public:
void DoSomething() override {
std::cout << "derive task do someting" << std::endl;
}
};
using DeriveTaskPtr = std::shared_ptr<DeriveTask>;
/// 示例用户
class User {
public:
User() { thread_pool_.Run(); }
~User() { thread_pool_.Stop(); }
void DoJobAsync(const BaseTaskPtr& task) {
// task 是 user->DoJob 调用产生的临时对象,捕获它的引用会变成也指针
thread_pool_.Post([&task] { task->DoSomething(); });
}
private:
MyThreadPool thread_pool_;
};
using UserPtr = std::shared_ptr<User>;
/// 测试运行出 core
int main() {
auto user = std::make_shared<User>();
DeriveTaskPtr derive_task1 = std::make_shared<DeriveTask>();
// derive_task 会隐式转换为 BaseTask 智能指针对象,
// 该对象是临时对象,在 DoJob 执行完之后生命周期结束。
user->DoJobAsync(derive_task1);
DeriveTaskPtr derive_task3 = std::make_shared<DeriveTask>();
user->DoJobAsync(derive_task3);
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
上面这个例子代码,会出现 coredump,或者是没有执行派生类的 DoSomething,总之是不符合预期。不符合预期的原因如下:这份代码往一个线程里 post lambda 函数,lambda 函数引用捕获智能指针对象,这是一个临时对象,其离开使用域之后会被析构掉,导致 lambda 函数在异步线程执行时,访问到一个"野引用"出错。而之所以捕获的智能指针是临时对象,是因为调用 User.DoJobAsync 时发生了类型的向上转换.
上述的例子还比较容易看出来问题点,但当我们的项目代码层次较深时,这类错误就非常难看出来,也因此之前团队里的资深同事也都无法发现问题所在.
这类问题有多种解决办法: (1)方法1:避免出现隐式转换,消除临时对象; (2)方法2:函数和 lambda 捕获都修改为裸指针,消除临时对象;引用本质上是指针,需要关注生命周期,既然采用引用参数就表示调用者需要保障对象的生命周期,智能指针的引用在用法上跟指针无异,那么这里不如用裸指针,让调用者更清楚自己需要保障对象的生命周期; (3)方法3:异步执行时采用值捕获/值传递,不采用引用捕获,但值捕获可能导致性能浪费,具体到本文的例子,这里的性能开销是一个智能指针对象的构造,性能损耗不大,是可接受的.
临时对象的生命周期可以参考这篇文档:https://en.cppreference.com/w/cpp/language/reference_initialization#Lifetime_of_a_temporary 。
最后此篇关于C++lambda引用捕获临时对象引发coredump的案例的文章就讲到这里了,如果你想了解更多关于C++lambda引用捕获临时对象引发coredump的案例的内容请搜索CFSDN的文章或继续浏览相关文章,希望大家以后支持我的博客! 。
这个问题在这里已经有了答案: Oracle: merging two different queries into one, LIKE & IN (1 个回答) 8年前关闭。 我有以下代码: case
我查阅过此页面:http://dev.mysql.com/doc/refman/5.1/en/case.html以及这个,但无法获得一个简单的程序来工作...... 更新:为了明确我想要做什么:我想从
有什么办法可以优化下面的查询吗? SELECT DATE_FORMAT(a.duedate,'%d-%b-%y') AS dte, duedate, SUM(CASE WHEN (typeofnoti
我进退两难,以下 SQL 查询的结果是什么以及它是如何工作的: SELECT ... CASE WHEN (a.FIELD=1 AND b.FIELD=2) THEN 1 WHEN
问题:输入年,月,打印对应年月的日历。 示例: 问题分析: 1,首先1970年是Unix系统诞生的时间,1970年成为Unix的元年,1970年1月1号是星期四,现在大多的手机的日历功能只能显
**摘要:**介绍了Angular中依赖注入是如何查找依赖,如何配置提供商,如何用限定和过滤作用的装饰器拿到想要的实例,进一步通过N个案例分析如何结合依赖注入的知识点来解决开发编程中会遇到的问题。 本
我想拥有自动伴侣类apply case 类的构造函数来为我执行隐式转换,但无法弄清楚如何这样做。我到处搜索,我能找到的最接近的答案是 this问题(我将解释为什么它不是我在下面寻找的)。 我有一个看起
您好,我已经浏览了“多列案例”问题,但没有看到与此相同的内容,所以我想我应该问一下。 基本上我有两个我想要连接的表(都是子查询的结果)。它们具有相同的列名称。如果我加入他们的 ID 和 SELECT
我发现了一些类型推断的非直觉行为。因此,语义等效代码的工作方式不同,具体取决于编译器推断出的有关函数返回类型的信息。当您在最小单元测试中重现此案例时,或多或少会清楚发生了什么。但我担心在编写框架代码时
CREATE TABLE test ( sts_id int , [status1] int , [status2] int , [status3] int , [status4] int ) INS
我有以下声明: SELECT Dag AS Dag, CASE Jaar WHEN 2013 THEN Levering END AS '2013', CASE
我想做的是为所有高于平均时间、平均时间和低于平均时间的游乐设施获取平均tip_portion。所以返回3行。当我运行它时,它显示: ERROR: missing FROM-clause entry
我正在尝试设置一个包含以下字段的报告: 非常需要报告来显示日期、该日期内的总记录(因此我按日期分组),然后按小时计算 12 小时工作日(从上午 8 点到晚上 8 点)我需要计算记录在这些时间内出现的时
我有这个查询 SELECT users.name FROM users LEFT JOIN weapon_stats ON users.id = weapon_stats.zp_id WHERE we
我正在尝试按收视率等级获取不同视频的计数。我有下表: vid_id views 1 6 1 10 1 900 2 850 2 125000
假设我有一个如下所示的 SQL 语句: select supplier, case when platform in (5,6) then 'mobile' when p
我有一个表测试 TestNumber (int primary key) InactiveBitwise (int) 我执行以下命令: UPDATE tests SET CASE WH
我有一个像这样的表(name=expense): id amount date 1 -1687 2014-01-02 00:00:00.0 2 11000 2014-01-02 0
我有一个 multimap 定义 typedef std::pair au_pair; //vertices typedef std::pair acq_pair; //ch qlty specifi
我有一个有点像枚举的类,它的每个实例都有一个唯一的 int 值,该值从 0 开始并在每个新实例时递增。 class MyEnumLikeClass { static int NextId =
我是一名优秀的程序员,十分优秀!