unordered_map多线程崩溃在find
崩溃最近程序中出现了崩溃现象,经过查询,发现是崩溃在STL容器的查询中。崩溃的截图如下:关于unordered_map无序 map 容器,unordered_map 容器不会像 map 容器那样对存储的数据进行排序。unordered_map 容器底层采用的是哈希表存储结构,该结构本身不具有对数据的排序功能,所以此容器内部不会自行对存储的键值对进行排序。关联容器删除一个元素的时候,当前的迭代器会失
崩溃
最近程序中出现了崩溃现象,经过查询,发现是崩溃在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线程安全容器。
更多推荐
所有评论(0)