std::map 的元素如何从映射中删除自身?

How an element of std::map can delete itself from the map?

提问人:shichao 提问时间:10/27/2023 最后编辑:Amitshichao 更新时间:10/30/2023 访问量:232

问:

我在不同的源文件中有两个类,class in 和 , class in 和 ,是一个类,它创建对象,并将其存储在映射中,当创建对象时,它会创建一个线程,我希望 s 对象从该映射中删除自己,当线程退出时,它是 的成员,但我的程序抛出异常。Aa.hppa.cppBb.hppb.cppASingletonBBBA

// main.cpp

#include <iostream>
#include "a.hpp"

int main() {
    A::get_instance().add(1);
    int pause = 0; std::cin >> pause;
    return 0;
}
// a.hpp

#ifndef __A_HPP__
#define __A_HPP__

#include <unordered_map>
#include <memory>
#include "b.hpp"

class A {
public:
    void add(int i);
    void remove(int i);
    static A& get_instance();
private:
    std::unordered_map<int, std::shared_ptr<B>> map_b_;
};


#endif
// a.cpp

#include "a.hpp"

void A::add(int i) {
    std::cout << "add: " << i << std::endl;
    map_b_[i] = std::move(std::make_shared<B>(i));
    std::this_thread::sleep_for(3s);
}
void A::remove(int i) {
    std::cout << "remove: " << i << std::endl;
    if (map_b_.count(i)) {
        map_b_.erase(i);
    }
}
A& A::get_instance() {
    static A instance;
    return instance;
}
// b.hpp

#ifndef __B_HPP__
#define __B_HPP__

#include <thread>
#include <chrono>
#include <atomic>
#include <iostream>
using namespace std::chrono_literals;

class B {
public:
    B(int id);
    ~B();
    void stop();
    void run();
private:
    std::atomic<bool> run_flag_;
    std::thread thr_;
    int id_;
};


#endif
b.cpp

#include "b.hpp"
#include "a.hpp"

B::B(int id) {
    id_ = id;
    run_flag_ = true;
    std::thread t(&B::run, this);
    thr_ = std::move(t);
}
B::~B() {
        std::cout << "~B(): " << id_ << std::endl;
    stop();
}
void B::stop() {
    run_flag_ = false;
    if (thr_.joinable()) {
        thr_.join();
    }
}
void B::run() {
    int count = 0;
    while (count < 3 && run_flag_) {
        std::cout << "processing id: " << id_ << std::endl;
        std::this_thread::sleep_for(1s);
        count ++;
    }
    A::get_instance().remove(id_);
}
# CMakeLists.txt
# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)

# 项目信息
project (demo)
SET(CMAKE_BUILD_TYPE "Debug")
# 指定生成目标
add_executable(
    ${PROJECT_NAME} 
    main.cpp
    a.cpp
    b.cpp
)
find_package(Threads REQUIRED) 
target_link_libraries(${PROJECT_NAME} Threads::Threads)

我已经在我的 Linux docker 中运行了代码,但它抛出了一个错误,就像这样,我想知道我该怎么做才能从容器中删除一个本身。

root@53d8817a43a8:~/test-cpp/build# ./demo
add: 1
processing id: 1
processing id: 1
processing id: 1
remove: 1
~B(): 1
terminate called after throwing an instance of 'std::system_error'
  what():  Resource deadlock avoided
Aborted
C++ 多线程 C++11 标准

评论

0赞 Pepijn Kramer 10/27/2023
旁注:全局变量没有互斥锁和线程......你会在某个时候遇到竞争条件。在 main 中创建一个 A 的实例,并确保它的寿命超过所有线程,或者对 A 进行shared_ptr,并将其副本传递给您启动的每个线程。
1赞 j6t 10/27/2023
我将使用以下方案:让 A 保持 s,将 a 传递给(分离的)线程,使 B 对象保持活动状态,直到线程结束。这将使 .然后在 ,垃圾回收过期。这样你就不需要了,这避免了调用中的争用条件。weak_ptrshared_ptrweak_ptrA::addweak_ptrA::removeremove
0赞 shichao 10/27/2023
有人刚刚给了我一个答案,但我不知道作者为什么删除它,他告诉我这个,没关系。this->thr_.detach();答::get_instance().remove(id_);
0赞 Ahmed AEK 10/27/2023
@shichao我删除了它,因为当应用程序尝试退出时,可能会发生一个条件,并且这是一个无法解决的竞争条件,您需要其他同步方式,基本上检查是否调用了其中一个或,然后不执行另一个,这将需要额外的原子。joindetachjoindetach
0赞 shichao 10/27/2023
@j6t我需要将 A 导出给其他人,我会保留很多 b 对象,并且在 A 中还有一些函数,其他函数需要通过 id 找到一个 b,更新或读取 b 中的某个成员,所以我需要保存它们,并在运行中更新映射。

答:

2赞 Amit 10/27/2023 #1

对于您的要求,我会选择该设计:

实现 Publish Subscribe 设计模式(源代码)。该类继承自该类。该类继承自该类。SingletonSubscriberBPublisher

该类具有 和 私有数据成员。increments 和 assigns 的构造函数。即每个对象都有一个唯一的 ID。因此,() 中的每个对象都有一个唯一的 id。 防止复制 B 对象很重要!Binline static std::atomic_uint64_t unique_{ 0 }uint64_t id_{ 0 }B++unique_id_ = unique_BBmap_std::unordered_map

  • 在添加:创建一个对象时:,获取其 id,将其指针添加到 ,,最后:调用以运行其线程。Bbmap_subscribe(b)b

该类有一个成员函数。由对象分离的线程获取对象成员函数的地址作为参数。当线程即将返回时,它会调用 .重要的是,只要一个对象的线程在运行,就不要从地图上删除/销毁它!Bvoid Done()BBDone()callbackcallback

调用的实现:函数(类)。B::Done()notify(id_)Publisher

由于该类是 a,因此它的实例在 B 对象线程的上下文中截获此调用。SingletonSubscribervoid update(Publisher* who, void* what = 0)

函数(覆盖 )将 转换为 (获取 id) 并从其 .Singleton::updateSubscribervoid* whatuint64_tBmap_

  • 删除时:在对象中查找:,按其 ID 查找,将其从 .map_Bbunsubscribe(b)map_

Add object 和 Remove object 的操作位于 2 个不同线程的上下文中,因此应使用 .也就是说,该类应该有一个( - M&M规则)。BBstd::lock_gaurd lk(mutex_)Singletonmutable std::mutex mutex_mutable