• C++ 实现线程安全的map(OpenHarmony源码实现版)


    概述

    STL容器不是线程安全的。比如对于vector,即使写方(生产者)是单线程写入,但是并发读的时候,由于潜在的内存重新申请和对象复制问题,会导致读方(消费者)的迭代器失效。实际表现也就是招致了core dump。另外一种情况,如果是多个写方,并发的push_back(),也会导致core dump。但可以通过固定vector的大小(调用resize())避免动态扩容(无push_back)来做到lock-free。

    c++的map的并发操作也是不安全的,c++里边有红黑树实现的std::map和hash表 unordered_map。在《C++并发编程实战》一书中的162页提供了一个细粒度锁的MAP数据结构,使用了 boost的shared_mutex (C++14已经支持,C++11没有),那上面的实现代码挺长的,这里给出个OpenHarmony源码实现的safe_map,代码精简,值得学习。

    接上篇欣赏了OpenHarmony源码中实现的ThreadPool的实现,链接在这里:

    c++的ThreadPool,OpenHarmony源码实现版赏析和使用

    源码实现

    源码位置:code-v3.0-LTS\OpenHarmony\utils\native\base\include\safe_map.h

    1. /*
    2. * Copyright (c) 2021 Huawei Device Co., Ltd.
    3. * Licensed under the Apache License, Version 2.0 (the "License");
    4. * you may not use this file except in compliance with the License.
    5. * You may obtain a copy of the License at
    6. *
    7. * http://www.apache.org/licenses/LICENSE-2.0
    8. *
    9. * Unless required by applicable law or agreed to in writing, software
    10. * distributed under the License is distributed on an "AS IS" BASIS,
    11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12. * See the License for the specific language governing permissions and
    13. * limitations under the License.
    14. */
    15. #ifndef UTILS_BASE_SAFE_MAP_H
    16. #define UTILS_BASE_SAFE_MAP_H
    17. #include
    18. #include
    19. namespace OHOS {
    20. template <typename K, typename V>
    21. class SafeMap {
    22. public:
    23. SafeMap() {}
    24. ~SafeMap() {}
    25. SafeMap(const SafeMap& rhs)
    26. {
    27. map_ = rhs.map_;
    28. }
    29. SafeMap& operator=(const SafeMap& rhs)
    30. {
    31. if (&rhs != this) {
    32. map_ = rhs.map_;
    33. }
    34. return *this;
    35. }
    36. V& operator[](const K& key)
    37. {
    38. return map_[key];
    39. }
    40. // when multithread calling size() return a tmp status, some threads may insert just after size() call
    41. int Size()
    42. {
    43. std::lock_guard lock(mutex_);
    44. return map_.size();
    45. }
    46. // when multithread calling Empty() return a tmp status, some threads may insert just after Empty() call
    47. bool IsEmpty()
    48. {
    49. std::lock_guard lock(mutex_);
    50. return map_.empty();
    51. }
    52. bool Insert(const K& key, const V& value)
    53. {
    54. std::lock_guard lock(mutex_);
    55. auto ret = map_.insert(std::pair(key, value));
    56. return ret.second;
    57. }
    58. void EnsureInsert(const K& key, const V& value)
    59. {
    60. std::lock_guard lock(mutex_);
    61. auto ret = map_.insert(std::pair(key, value));
    62. // find key and cannot insert
    63. if (!ret.second) {
    64. map_.erase(ret.first);
    65. map_.insert(std::pair(key, value));
    66. return;
    67. }
    68. return;
    69. }
    70. bool Find(const K& key, V& value)
    71. {
    72. bool ret = false;
    73. std::lock_guard lock(mutex_);
    74. auto iter = map_.find(key);
    75. if (iter != map_.end()) {
    76. value = iter->second;
    77. ret = true;
    78. }
    79. return ret;
    80. }
    81. bool FindOldAndSetNew(const K& key, V& oldValue, const V& newValue)
    82. {
    83. bool ret = false;
    84. std::lock_guard lock(mutex_);
    85. if (map_.size() > 0) {
    86. auto iter = map_.find(key);
    87. if (iter != map_.end()) {
    88. oldValue = iter->second;
    89. map_.erase(iter);
    90. map_.insert(std::pair(key, newValue));
    91. ret = true;
    92. }
    93. }
    94. return ret;
    95. }
    96. void Erase(const K& key)
    97. {
    98. std::lock_guard lock(mutex_);
    99. map_.erase(key);
    100. }
    101. void Clear()
    102. {
    103. std::lock_guard lock(mutex_);
    104. map_.clear();
    105. return;
    106. }
    107. private:
    108. std::mutex mutex_;
    109. std::map map_;
    110. };
    111. } // namespace OHOS
    112. #endif

    源码欣赏

    使用模板语法template 让这个map的实现更通用。这是c++模板泛型的强大之处,不用针对每个类型都实现一遍,复用性更强。且模板是在编译期检查的,也降低的出错的可能性。内部实现上,倒是没啥特别的,就是对相应的操作加了锁。锁使用的RAII模型的std::lock_guard写法,这种很常见也很常用。

    自定义实现了几个常用的操作方法如Find,Erase和Clear,每个里面的操作都相应的加了锁。操作符重载实现了[]和赋值=操作。注意这两处的地方没有用锁,你知道为什么吗?如果多个线程只访问容器但不更改其结构,则不需要对容器进行同步。另外一个原因是,对于map,如果只是通过[]的方式修改而不是新插入,则多线程下也不会core dump。

    单元测试

    源码中同样有safe_map的单元测试,单元测试框架使用的是google的gtest。看来gtest还是很强大的,华为也选择使用了它。以下给出源码,可以熟悉下gtest单元测试的用法。

    1. /*
    2. * Copyright (c) 2021 Huawei Device Co., Ltd.
    3. * Licensed under the Apache License, Version 2.0 (the "License");
    4. * you may not use this file except in compliance with the License.
    5. * You may obtain a copy of the License at
    6. *
    7. * http://www.apache.org/licenses/LICENSE-2.0
    8. *
    9. * Unless required by applicable law or agreed to in writing, software
    10. * distributed under the License is distributed on an "AS IS" BASIS,
    11. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12. * See the License for the specific language governing permissions and
    13. * limitations under the License.
    14. */
    15. #include "safe_map.h"
    16. #include
    17. #include
    18. #include
    19. #include
    20. #include
    21. #include
    22. #include // std::chrono::seconds
    23. #include // std::cout
    24. #include // std::thread, std::this_thread::sleep_for
    25. using namespace testing::ext;
    26. using namespace OHOS;
    27. using namespace std;
    28. class UtilsSafeMap : public testing::Test {
    29. };
    30. /*
    31. * @tc.name: testUtilsCopyAndAssign001
    32. * @tc.desc: single thread test the normal feature insert and erase and EnsureInsert
    33. */
    34. HWTEST_F(UtilsSafeMap, testUtilsCopyAndAssign001, TestSize.Level1)
    35. {
    36. SafeMapint> demoData;
    37. // insert new
    38. demoData.Insert("A", 1);
    39. ASSERT_FALSE(demoData.IsEmpty());
    40. ASSERT_EQ(demoData.Size(), 1);
    41. SafeMapint> newdemo = demoData;
    42. int tar = -1;
    43. ASSERT_TRUE(newdemo.Find("A", tar));
    44. ASSERT_EQ(1, tar);
    45. tar = -1;
    46. SafeMapint> newdemo2;
    47. newdemo2 = demoData;
    48. ASSERT_TRUE(newdemo2.Find("A", tar));
    49. ASSERT_EQ(1, tar);
    50. }
    51. /*
    52. * @tc.name: testUtilsoperator001
    53. * @tc.desc: SafeMap
    54. */
    55. HWTEST_F(UtilsSafeMap, testUtilsoperator001, TestSize.Level1)
    56. {
    57. SafeMapint> demoData;
    58. // insert new
    59. demoData.Insert("A", 1);
    60. ASSERT_FALSE(demoData.IsEmpty());
    61. ASSERT_EQ(demoData.Size(), 1);
    62. ASSERT_EQ(demoData["A"], 1);
    63. SafeMapint> newdemo = demoData;
    64. ASSERT_EQ(newdemo["A"], 1);
    65. int tar = -1;
    66. newdemo["B"] = 6;
    67. ASSERT_TRUE(newdemo.Find("B", tar));
    68. ASSERT_EQ(6, tar);
    69. SafeMapint> newdemo2;
    70. newdemo2 = newdemo;
    71. ASSERT_EQ(newdemo2["A"], 1);
    72. }
    73. /*
    74. * @tc.name: testUtilsNormalFeatureInsert001
    75. * @tc.desc: SafeMap
    76. */
    77. HWTEST_F(UtilsSafeMap, testUtilsNormalFeatureInsert001, TestSize.Level1)
    78. {
    79. SafeMapint> demoData;
    80. ASSERT_TRUE(demoData.IsEmpty());
    81. // insert new
    82. demoData.Insert("A", 1);
    83. ASSERT_FALSE(demoData.IsEmpty());
    84. ASSERT_EQ(demoData.Size(), 1);
    85. // insert copy one should fail
    86. ASSERT_FALSE(demoData.Insert("A", 2));
    87. ASSERT_EQ(demoData.Size(), 1);
    88. }
    89. /*
    90. * @tc.name: testUtilsNormalFeatureEnsureInsert001
    91. * @tc.desc: SafeMap
    92. */
    93. HWTEST_F(UtilsSafeMap, testUtilsNormalFeatureEnsureInsert001, TestSize.Level1)
    94. {
    95. SafeMapint> demoData;
    96. ASSERT_TRUE(demoData.IsEmpty());
    97. demoData.Insert("A", 1);
    98. demoData.EnsureInsert("B", 2);
    99. ASSERT_FALSE(demoData.IsEmpty());
    100. ASSERT_EQ(demoData.Size(), 2);
    101. // insert copy one and new one
    102. demoData.EnsureInsert("B", 5);
    103. demoData.EnsureInsert("C", 6);
    104. ASSERT_EQ(demoData.Size(), 3);
    105. }
    106. /*
    107. * @tc.name: testUtilsNormalFeatureFind001
    108. * @tc.desc: SafeMap
    109. */
    110. HWTEST_F(UtilsSafeMap, testUtilsNormalFeatureFind001, TestSize.Level1)
    111. {
    112. SafeMapint> demoData;
    113. ASSERT_TRUE(demoData.IsEmpty());
    114. demoData.Insert("A", 1);
    115. demoData.Insert("B", 10000);
    116. demoData.EnsureInsert("B", 2);
    117. demoData.EnsureInsert("C", 6);
    118. ASSERT_FALSE(demoData.IsEmpty());
    119. ASSERT_EQ(demoData.Size(), 3);
    120. int i = 0;
    121. ASSERT_TRUE(demoData.Find("A", i));
    122. ASSERT_EQ(i, 1);
    123. ASSERT_TRUE(demoData.Find("B", i));
    124. ASSERT_EQ(i, 2);
    125. ASSERT_TRUE(demoData.Find("C", i));
    126. ASSERT_EQ(i, 6);
    127. }
    128. /*
    129. * @tc.name: testUtilsNormalFeatureFindAndSet001
    130. * @tc.desc: SafeMap
    131. */
    132. HWTEST_F(UtilsSafeMap, testUtilsNormalFeatureFindAndSet001, TestSize.Level1)
    133. {
    134. SafeMapint> demoData;
    135. ASSERT_TRUE(demoData.IsEmpty());
    136. demoData.Insert("A", 1);
    137. demoData.EnsureInsert("B", 2);
    138. int oldvalue = 0;
    139. int newvalue = 3;
    140. ASSERT_TRUE(demoData.FindOldAndSetNew("A", oldvalue, newvalue));
    141. // old value
    142. ASSERT_EQ(oldvalue, 1);
    143. newvalue = 4;
    144. ASSERT_TRUE(demoData.FindOldAndSetNew("B", oldvalue, newvalue));
    145. // old value
    146. ASSERT_EQ(oldvalue, 2);
    147. int i = -1;
    148. ASSERT_TRUE(demoData.Find("A", i));
    149. // new value
    150. ASSERT_EQ(i, 3);
    151. ASSERT_TRUE(demoData.Find("B", i));
    152. // new value
    153. ASSERT_EQ(i, 4);
    154. }
    155. /*
    156. * @tc.name: testUtilsNormalFeatureEraseAndClear001
    157. * @tc.desc: SafeMap
    158. */
    159. HWTEST_F(UtilsSafeMap, testUtilsNormalFeatureEraseAndClear001, TestSize.Level1)
    160. {
    161. SafeMapint> demoData;
    162. ASSERT_TRUE(demoData.IsEmpty());
    163. demoData.Insert("A", 1);
    164. demoData.EnsureInsert("B", 2);
    165. ASSERT_EQ(demoData.Size(), 2);
    166. demoData.Erase("A");
    167. ASSERT_EQ(demoData.Size(), 1);
    168. demoData.Clear();
    169. ASSERT_EQ(demoData.Size(), 0);
    170. }
    171. /*
    172. * @tc.name: testUtilsConcurrentWriteAndRead001
    173. * @tc.desc: 100 threads test in writein to the same key of the map, while read at same time and no throw
    174. */
    175. const int THREAD_NUM = 100;
    176. HWTEST_F(UtilsSafeMap, testUtilsConcurrentWriteAndRead001, TestSize.Level1)
    177. {
    178. SafeMapint> demoData;
    179. std::thread threads[THREAD_NUM];
    180. std::thread checkThread[THREAD_NUM];
    181. ASSERT_NO_THROW({
    182. auto lamfuncInsert = [](SafeMapint>& data, const string& key,
    183. const int& value, const std::chrono::system_clock::time_point& absTime) {
    184. std::this_thread::sleep_until(absTime);
    185. data.EnsureInsert(key, value);
    186. };
    187. auto lamfuncCheck = [](SafeMapint>& data, const string& key,
    188. std::chrono::system_clock::time_point absTime) {
    189. std::this_thread::sleep_until(absTime);
    190. thread_local int i = -1;
    191. data.Find(key, i);
    192. };
    193. using std::chrono::system_clock;
    194. std::time_t timeT = system_clock::to_time_t(system_clock::now());
    195. timeT += 2;
    196. string key("A");
    197. for (int i = 0; i < THREAD_NUM; ++i) {
    198. threads[i] = std::thread(lamfuncInsert, std::ref(demoData), key, i, system_clock::from_time_t(timeT));
    199. checkThread[i] = std::thread(lamfuncCheck, std::ref(demoData), key, system_clock::from_time_t(timeT));
    200. }
    201. std::this_thread::sleep_for(std::chrono::seconds(3));
    202. for (auto& t : threads) {
    203. t.join();
    204. }
    205. for (auto& t : checkThread) {
    206. t.join();
    207. }
    208. });
    209. }
    210. /*
    211. * @tc.name: testUtilsConcurrentWriteAndFind001
    212. * @tc.desc: 100 threads test in writein to the corresponding key of the map,
    213. * while read at same time and check the results
    214. */
    215. HWTEST_F(UtilsSafeMap, testUtilsConcurrentWriteAndFind001, TestSize.Level1)
    216. {
    217. SafeMapint> demoData;
    218. std::thread threads[THREAD_NUM];
    219. std::vectorint>> vcfi;
    220. ASSERT_NO_THROW({
    221. auto lamfuncInsert = [](SafeMapint>& data, const string& key,
    222. const int& value, const std::chrono::system_clock::time_point& absTime) {
    223. std::this_thread::sleep_until(absTime);
    224. data.EnsureInsert(key, value);
    225. };
    226. auto lamfuncCheckLoop = [](SafeMapint>& data, const string& key,
    227. std::chrono::system_clock::time_point absTime) {
    228. std::this_thread::sleep_until(absTime);
    229. thread_local int i = -1;
    230. while (!data.Find(key, i)) {
    231. std::this_thread::sleep_for(std::chrono::microseconds(10));
    232. }
    233. return i;
    234. };
    235. using std::chrono::system_clock;
    236. std::time_t timeT = system_clock::to_time_t(system_clock::now());
    237. timeT += 2;
    238. string key("A");
    239. for (int i = 0; i < THREAD_NUM; ++i) {
    240. threads[i] = std::thread(lamfuncInsert, std::ref(demoData),
    241. key + std::to_string(i), i, system_clock::from_time_t(timeT));
    242. vcfi.push_back(std::async(std::launch::async, lamfuncCheckLoop,
    243. std::ref(demoData), key + std::to_string(i), system_clock::from_time_t(timeT)));
    244. }
    245. std::this_thread::sleep_for(std::chrono::seconds(4));
    246. for (auto& t : threads) {
    247. t.join();
    248. }
    249. vector<int> result;
    250. for (auto& t : vcfi) {
    251. result.push_back(t.get());
    252. }
    253. std::sort(result.begin(), result.end());
    254. for (int i = 0; i < THREAD_NUM; ++i) {
    255. ASSERT_EQ(i, result[i]);
    256. }
    257. });
    258. }
    259. /*
    260. * @tc.name: testUtilsConcurrentWriteAndFindAndSet001
    261. * @tc.desc: 100 threads test in writein to the corresponding key of the map,
    262. * while findandfix at same time and check the results
    263. */
    264. HWTEST_F(UtilsSafeMap, testUtilsConcurrentWriteAndFindAndSet001, TestSize.Level1)
    265. {
    266. SafeMapint> demoData;
    267. std::thread threads[THREAD_NUM];
    268. std::vectorint>> vcfi;
    269. ASSERT_NO_THROW({
    270. auto lamfuncInsert = [](SafeMapint>& data, const string& key,
    271. const int& value, const std::chrono::system_clock::time_point& absTime) {
    272. std::this_thread::sleep_until(absTime);
    273. data.EnsureInsert(key, value);
    274. };
    275. auto lamfuncCheckLoop = [](SafeMapint>& data, const string& key,
    276. const int& newvalue, std::chrono::system_clock::time_point absTime) {
    277. std::this_thread::sleep_until(absTime);
    278. thread_local int i = -1;
    279. while (!data.FindOldAndSetNew(key, i, newvalue)) {
    280. std::this_thread::sleep_for(std::chrono::microseconds(10));
    281. }
    282. return i;
    283. };
    284. using std::chrono::system_clock;
    285. std::time_t timeT = system_clock::to_time_t(system_clock::now());
    286. timeT += 2;
    287. string key("A");
    288. for (int i = 0; i < THREAD_NUM; ++i) {
    289. threads[i] = std::thread(lamfuncInsert, std::ref(demoData),
    290. key + std::to_string(i), i, system_clock::from_time_t(timeT));
    291. vcfi.push_back(std::async(std::launch::async, lamfuncCheckLoop,
    292. std::ref(demoData), key + std::to_string(i), i + 1, system_clock::from_time_t(timeT)));
    293. }
    294. std::this_thread::sleep_for(std::chrono::seconds(4));
    295. for (auto& t : threads) {
    296. t.join();
    297. }
    298. vector<int> result;
    299. for (auto& t : vcfi) {
    300. result.push_back(t.get());
    301. }
    302. std::sort(result.begin(), result.end());
    303. for (int i = 0; i < THREAD_NUM; ++i) {
    304. ASSERT_EQ(i, result[i]);
    305. }
    306. int t = 0;
    307. result.clear();
    308. for (int i = 0; i < THREAD_NUM; ++i) {
    309. t = -1;
    310. ASSERT_TRUE(demoData.Find("A" + std::to_string(i), t));
    311. result.push_back(t);
    312. }
    313. std::sort(result.begin(), result.end());
    314. for (int i = 0; i < THREAD_NUM; ++i) {
    315. ASSERT_EQ(i + 1, result[i]);
    316. }
    317. });
    318. }

    引用

    C++线程安全map (低效率) | 码农家园

    C++11:基于std::queue和std::mutex构建一个线程安全的队列_10km的博客-CSDN博客_std::queue 线程安全

    C++11:基于std::unordered_map和共享锁构建线程安全的map_10km的博客-CSDN博客_c++ map 无锁

    如何设计并实现一个线程安全的 Map ?(上篇) - JavaShuo

    c++线程安全的map_clh01s的博客-CSDN博客_c++ 线程安全的map

    C++ 20 线程安全的Map_学习好烦啊的博客-CSDN博客_c++ map 线程安全

    c++ map 多线程_线程安全原理简析及HashMap多线程并发5种场景异常分析_sumilao的博客-CSDN博客

    C++ STL容器如何解决线程安全的问题? - 腾讯云开发者社区-腾讯云

    RISC-V MCU中文社区_致力于RISC-V技术的推广,提供一个交流学习的开放平台

  • 相关阅读:
    springboot集成mybatis
    ubuntu18安装caffe(CPU)
    石油数字孪生可视化管理平台,推动石油行业数字化转型与智能化应用
    《10人以下小团队管理手册》推荐书评
    webpack-cli 在 webpack 打包中的作用
    如何实现Spring中服务关闭时对象销毁执行代码
    Rust基本数据类型-字符串
    Vuex源码分析,实现简易版的Vuex
    一句话解释什么是出口IP
    PMP是什么?
  • 原文地址:https://blog.csdn.net/qq8864/article/details/127435180