崩溃

最近程序中出现了崩溃现象,经过查询,发现是崩溃在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线程安全容器。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐