unordered_map多线程崩溃在find

崩溃

最近程序中出现了崩溃现象,经过查询,发现是崩溃在STL容器的查询中。

崩溃的截图如下:

在这里插入图片描述

关于unordered_map

无序 map 容器,unordered_map 容器不会像 map 容器那样对存储的数据进行排序。

unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。

关联容器删除一个元素的时候,当前的迭代器会失效,其他的迭代器不会失效,增加一个元素的时候,迭代器不会失效。

线程安全性的保证:

  • 多线程同时读
  • 单线程写

也就是说,map容器并不保证读写的线程安全性。

如果一个线程写,同时其他线程读的话,就会存在并发的问题,可能导致崩溃。

测试代码

可以很简单地写一下测试程序:

#include <unordered_map>
#include <map>
#include <future>
#include <string>
#include <vector>
#include <thread>
#include <iostream>
#include <unistd.h>
#include <sys/syscall.h>                 /* For SYS_xxx definitions */

using namespace std;

unordered_map<string, double> test_map;

string code = "000001.SZ";

void write_map()
{
    cout << "write_map start, threadid: " << syscall(SYS_gettid) << endl;

    while (true) {
        test_map[code] = 3;
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
}

void read_map()
{
    cout << "read_map start, threadid: " << syscall(SYS_gettid) << endl;
    double price;
    while (true) {
        auto iter = test_map.find(code);
        if (iter == test_map.end()) {
            cout << "not found|" << endl;
            continue;
        }
        price = iter->second;
    }
}

int main()
{
    vector<thread> threads_;
    for (int i = 0; i < 1; ++i) {
        threads_.emplace_back(std::thread(write_map));
    }
    for (int i = 10; i < 20; ++i) {
        threads_.emplace_back(std::thread(read_map));
    }

    for (std::thread& t : threads_) {
        if (t.joinable()) {
            t.join();
        }
    }

    return 0;
}

编译运行,打开core文件转储,运行几次就得到了core文件。

gdb调试就会看到本文开头的调试信息。

std::shared_mutex

使用mutex可以解决数据竞争的问题,但这会影响系统性能。

而c++17中引入了std::shared_mutex,用于管理可转移和共享所有权的互斥对象。

它适用场景比较特殊:一个或多个读线程同时读取共享资源,且只有一个写线程来写这个资源,这种情况下才能从shared_mutex获取性能优势。

使用std::shared_mutex的示例代码如下:

// from https://en.cppreference.com/w/cpp/thread/shared_mutex
#include <iostream>
#include <mutex>
#include <shared_mutex>
#include <thread>
 
class ThreadSafeCounter {
 public:
  ThreadSafeCounter() = default;
 
  // Multiple threads/readers can read the counter's value at the same time.
  unsigned int get() const {
    std::shared_lock lock(mutex_);
    return value_;
  }
 
  // Only one thread/writer can increment/write the counter's value.
  unsigned int increment() {
    std::unique_lock lock(mutex_);
    return ++value_;
  }
 
  // Only one thread/writer can reset/write the counter's value.
  void reset() {
    std::unique_lock lock(mutex_);
    value_ = 0;
  }
 
 private:
  mutable std::shared_mutex mutex_;
  unsigned int value_ = 0;
};
 
int main() {
  ThreadSafeCounter counter;
 
  auto increment_and_print = [&counter]() {
    for (int i = 0; i < 3; i++) {
      std::cout << std::this_thread::get_id() << ' ' << counter.increment() << '\n';
 
      // Note: Writing to std::cout actually needs to be synchronized as well
      // by another std::mutex. This has been omitted to keep the example small.
    }
  };
 
  std::thread thread1(increment_and_print);
  std::thread thread2(increment_and_print);
 
  thread1.join();
  thread2.join();
}
 
// Explanation: The output below was generated on a single-core machine. When
// thread1 starts, it enters the loop for the first time and calls increment()
// followed by get(). However, before it can print the returned value to
// std::cout, the scheduler puts thread1 to sleep and wakes up thread2, which
// obviously has time enough to run all three loop iterations at once. Back to
// thread1, still in the first loop iteration, it finally prints its local copy
// of the counter's value, which is 1, to std::cout and then runs the remaining
// two loop iterations. On a multi-core machine, none of the threads is put to
// sleep and the output is more likely to be in ascending order.
小结

对于STL容器,线程安全性是需要慎重考虑的地方。

使用锁可以简单地解决这类数据竞争问题。

也可以考虑使用其他方式,如线程安全STL或者boost线程安全容器。

C++的unordered_map容器中,counter是指存储在容器中的键值对的计数器。它用来记录每个键对应的值在容器中出现的次数。unordered_map容器底层采用的是哈希表存储结构,不具备排序功能,因此存储的键值对并不会按照某种特定的顺序进行排序。 要在unordered_map中实现计数器的功能,可以使用以下方法: 1. 使用insert函数或[]操作符将新的键值对插入到容器中,并设置初始计数为1。 2. 当要增加某个键对应的计数时,可以使用[]操作符来访问该键,并递增其值。 3. 当要减少某个键对应的计数时,可以使用[]操作符来访问该键,并递减其值。 4. 若要删除某个键,可以使用erase函数来删除该键及其对应的值。 使用增强for循环遍历unordered_map容器,可以通过auto&来声明迭代变量,从而获得每个键值对的引用,然后可以分别访问键和值来进行操作。 请注意,unordered_map容器内部并不会按照存储顺序对键值对进行排序,因此在遍历容器时,键值对的顺序是不确定的。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [c++ unordered_map总结](https://blog.csdn.net/weixin_43239347/article/details/119686158)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [unordered_map多线程崩溃在find](https://blog.csdn.net/guotianqing/article/details/120440508)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值