C++编程:使用std::weak_ptr监控std::shared_ptr解决多线程竞态实例(智能指针失效)

news/2024/8/26 4:32:25 标签: c++, weak_ptr, shared_ptr, 多线程, 竟态, 不安全

文章目录

    • 0. 概要
    • 1. 实例代码
      • 2. 线程安全机制与智能指针
      • 3. 安全与非安全消费者线程设计对比
      • 4. 跨模块传递`std::weak_ptr`的一致性与循环引用预防
      • 5. 执行结果

0. 概要

[前置阅读] C++编程:使用std::weak_ptr监控std::shared_ptr

为了展示竞态条件的可能性,并且验证更安全的代码是如何避免这种竞态条件的,可以创建一个简单的多线程测试程序。
我们将使用一个生产者-消费者模型,其中生产者向队列中添加带有 std::shared_ptr 的事件,而消费者则从队列中取出事件并处理它们。

我们将实现两个版本:

  1. 不安全版本:不检查 std::shared_ptr 是否有效
  2. 安全的版本:消费者线程在持有锁的情况下检查 std::shared_ptr 的有效性。

1. 实例代码

以下是一个使用 C++11 标准库编写的测试程序示例:

#include <atomic>
#include <condition_variable>
#include <functional>
#include <iostream>
#include <memory>
#include <mutex>
#include <queue>
#include <thread>
#include <vector>

struct Event {
  int id;
};

class EventQueue {
 public:
  void push_event(const Event &e, std::shared_ptr<void> ptr) {
    std::unique_lock<std::mutex> lck(mtx_);
    events_.push({e, std::weak_ptr<void>(ptr)});
    cv_.notify_one();
  }

  void consume_events(
    std::function<void(Event, std::shared_ptr<void>)> callback) {
    std::unique_lock<std::mutex> lck(mtx_);
    while (true) {
      cv_.wait(lck, [this] { return !events_.empty() || stop_.load(); });

      if (stop_.load()) {
        break;
      }

      auto event_item = events_.front();
      events_.pop();

#ifndef UN_SAFE
      // 安全版本:在持有锁的情况下检查 shared_ptr 是否有效
      if (const auto &shared_ptr = event_item.second.lock()) {
        if (callback != nullptr) {
          callback(event_item.first, shared_ptr);
        }
      }
#else
      // 不安全版本:不检查 shared_ptr 是否有效
      if (callback != nullptr) {
        callback(event_item.first, event_item.second.lock());
      }
#endif
    }
  }

  void stop_consuming() {
    stop_.store(true);
    cv_.notify_all();
  }

 private:
  std::queue<std::pair<Event, std::weak_ptr<void>>> events_;
  std::mutex mtx_;
  std::condition_variable cv_;
  std::atomic<bool> stop_{false};
};

void producer(EventQueue &eq, int num_events) {
  for (int i = 0; i < num_events; ++i) {
    Event e{i};
    std::shared_ptr<void> ptr(new int(i));
    eq.push_event(e, ptr);
    std::this_thread::sleep_for(std::chrono::milliseconds(1));  // 模拟耗时操作
  }
}

void consumer(EventQueue &eq, int num_events) {
  eq.consume_events([](Event e, std::shared_ptr<void> ptr) {
    std::cout << "Event ID: " << e.id << ", Pointer valid: " << std::boolalpha
              << (ptr.get() != nullptr) << std::endl;
  });
}

int main() {
  // 不安全的代码设计,一般跑不了1000次循环测试。
  static uint32_t count = 1;
  while (count < 1000) {
    EventQueue eq;
    std::thread prod(producer, std::ref(eq), 10);
    std::thread cons(consumer, std::ref(eq), 10);

    prod.join();
    eq.stop_consuming();
    cons.join();
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
    std::cout << "\n----- "
              << "count:" << count++ << " -----\n";
  }

  return 0;
}

2. 线程安全机制与智能指针

  • 智能指针与互斥锁的协同工作:利用std::lock_guard确保在持有互斥锁的情况下安全地操作std::shared_ptr,有效防止竞态条件的发生。
  • 原子变量控制线程终止:在stop_consuming()函数中,使用std::atomic<bool>类型的stop_变量来安全地控制消费者线程的终止,保证线程间通信的原子性。
  • 生产者-消费者模式下的线程安全:生产者线程通过push_event向事件队列添加事件,消费者线程则使用consume_events处理事件,确保所有操作在多线程环境下既安全又高效。

3. 安全与非安全消费者线程设计对比

  • 测试代码的核心部分

    #ifndef UN_SAFE
      // 安全版本:在持有锁的情况下检查 shared_ptr 是否有效
      if (const auto &shared_ptr = event_item.second.lock()) {
        if (callback != nullptr) {
          callback(event_item.first, shared_ptr);
        }
      }
    #else
      // 不安全版本:不检查 shared_ptr 是否有效
      if (callback != nullptr) {
        callback(event_item.first, event_item.second.lock());
      }
    #endif
    
  • 非安全实现的问题:当#ifdef UN_SAFE条件下,消费者线程在持有互斥锁时直接调用event_item.second.lock()来获取shared_ptr,并将其作为参数传递给回调函数callback,但缺乏对shared_ptr是否为nullptr的检查。这可能导致在对象已被销毁的情况下访问空shared_ptr,引发未定义行为或程序崩溃。

  • 安全实现的策略:通过先检查shared_ptr的有效性再调用回调函数的方法,确保资源在被访问前仍然有效。具体做法是:

    if (const auto &shared_ptr = event_item.second.lock()) {
      if (callback != nullptr) {
        callback(event_item.first, shared_ptr);
      }
    }
    

    这样一来,即使在事件处理期间与该事件关联的对象被销毁,也不会导致空指针访问或悬空引用的情况发生。

weak_ptr_150">4. 跨模块传递std::weak_ptr的一致性与循环引用预防

  • 跨模块传递的挑战与解决方案:在不同模块间传递std::weak_ptr时,重要的是确保它们引用的是相同的控制块。为了解决这个问题,可以共享同一个std::shared_ptr实例,或者显式地传递以确保控制块的一致性。
  • 选择std::weak_ptr而非std::shared_ptr的原因
    • 避免循环引用:如果事件类型内部已经使用了std::shared_ptr,那么在事件队列中继续使用std::shared_ptr可能会形成循环引用,阻碍资源的正常释放。
    • 延迟强引用提升:消费者线程在处理事件时可以根据需要通过lock()方法升级std::weak_ptrstd::shared_ptr,这样避免了不必要的强引用创建和维持,提高了性能和资源管理效率。

5. 执行结果

  • 测试不安全代码
    使用如下命令编译和执行,在第55次的时候出现了错误
g++ -o unsafe test.cpp -lpthread -DUN_SAFE -O2
./unsafe 

在这里插入图片描述

  • 测试安全代码
g++ -o unsafe test.cpp -lpthread  -O2
./unsafe 

在运行1000多次后,一次错误均未发生。


http://www.niftyadmin.cn/n/5558656.html

相关文章

Tita的OKR:高端制造行业的OKR案例

高端设备制造行业的发展趋势&#xff1a; 产业规模持续扩大&#xff1a;在高技术制造业方面&#xff0c;航空、航天器及设备制造业、电子工业专用设备制造等保持较快增长。新能源汽车保持产销双增&#xff0c;新材料新产品生产也高速增长。 标志性装备不断突破&#xff1a;例如…

uniapp字符串转base64,无需导入依赖(多端支持)

使用示例 import { Base64Encode, Base64Decode } from "@/utils/base64.js" base64.js const _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";export const Base64Encode = (text)

无人驾驶时代,萝卜快跑率先起飞

作者 | 三石 编辑 | 魏力 发布 | 大力财经 近日&#xff0c;全国密集落地了无人驾驶&#xff1a;7月16日上海面向普通市民的无驾驶人智能网联汽车实地测试&#xff1b;青岛公布第一批智能网联汽车开放测试道路目录&#xff1b;7月7日&#xff0c;北京正式开放智能…

比较HTTP/1.1、HTTP/2

HTTP/1.1和HTTP/2是两个不同版本的超文本传输协议&#xff08;HTTP&#xff09;&#xff0c;它们在多个方面存在显著的差异。以下是对这两个协议的比较&#xff1a; 一、连接管理 HTTP/1.1&#xff1a; 使用持久连接&#xff08;Persistent Connections&#xff09;&#xff…

Linux下如何安装配置Graylog日志管理工具

Graylog是一个开源的日志管理工具&#xff0c;可以帮助我们收集、存储和分析大量的日志数据。它提供了强大的搜索、过滤和可视化功能&#xff0c;可以帮助我们轻松地监控系统和应用程序的运行情况。 在Linux系统下安装和配置Graylog主要包括以下几个步骤&#xff1a; 准备安装…

【学习笔记】无人机(UAV)在3GPP系统中的增强支持(二)-支持高分辨率视频直播应用

引言 本文是3GPP TR 22.829 V17.1.0技术报告&#xff0c;专注于无人机&#xff08;UAV&#xff09;在3GPP系统中的增强支持。文章提出了多个无人机应用场景&#xff0c;分析了相应的能力要求&#xff0c;并建议了新的服务级别要求和关键性能指标&#xff08;KPIs&#xff09;。…

哈希表(知识点+leetcode)以及字符串哈希

文章目录 一、什么是哈希表二、哈希表常见结构介绍leetcode经典例题242 有效的字母异位词思路编程 349 两个数组的交集思路编程 1 两数之和思路编程 454 四数相加II思路编程 字符串哈希前言思路编程 一、什么是哈希表 哈希表是散列表&#xff0c;就是通过关键码值而直接进行访…

如何使用在线工具将手机相册中的图片转换为JPG格式

我们经常在手机相册中保存大量的图片&#xff0c;无论是家庭聚会的照片还是旅行的瞬间&#xff0c;每一幅图像都承载着珍贵的记忆。然而&#xff0c;有时候我们会遇到图片格式不兼容的问题&#xff0c;尤其是在需要将图片分享到特定平台或编辑时。 例如&#xff0c;某些社交平台…