安全容器模块详解

🔒 一句话概括:安全容器就是"带锁的收纳盒",多个线程同时访问也不会出错。


📚 目录

  1. 什么是安全容器?
  2. 为什么需要安全容器?
  3. 安全容器家族一览
  4. SafeMap - 线程安全字典
  5. SafeQueue - 线程安全队列
  6. SafeStack - 线程安全栈
  7. SafeBlockQueue - 阻塞队列
  8. SafeBlockQueueTracking - 可追踪阻塞队列
  9. SortedVector - 自动排序向量
  10. 使用场景与最佳实践

1. 什么是安全容器?

1.1 通俗理解

想象一个场景:你和同事共用一个文件柜 📁

普通容器(不安全)

你:打开柜子,准备放文件...
同事:同时打开柜子,拿走了你要放的位置的文件...
你:放文件... 💥 冲突了!

安全容器(线程安全)

你:拿钥匙🔑,锁住柜子,放文件,解锁
同事:等你解锁后,再拿钥匙,操作柜子
结果:井然有序 ✅

1.2 技术定义

安全容器 = 标准容器 + 互斥锁(Mutex)

它们在内部自动处理加锁/解锁,让多线程访问变得安全。

安全容器
加锁
SafeMap
操作数据
解锁
普通容器
直接操作数据
std::map

2. 为什么需要安全容器?

2.1 多线程的数据竞争问题

线程1 std::map 线程2 读取 map["key"] 值 = 100 同时写入 map["key"] = 200 💥 数据竞争! 使用读到的值... 可能是100,可能是200,可能崩溃! 线程1 std::map 线程2

2.2 安全容器如何解决

线程1 互斥锁 内部map 线程2 请求加锁 获得锁 🔒 安全读取 map["key"] 请求加锁 等待中...⏳ 释放锁 🔓 获得锁 🔒 安全写入 map["key"] = 200 释放锁 🔓 线程1 互斥锁 内部map 线程2

3. 安全容器家族一览

安全容器家族
SafeMap
线程安全字典
SafeQueue
线程安全队列
SafeStack
线程安全栈
SafeBlockQueue
阻塞队列
SafeBlockQueueTracking
可追踪阻塞队列
SortedVector
自动排序向量

对比总结表

容器底层结构线程安全阻塞支持排序主要用途
SafeMapstd::map按Key多线程键值存储
SafeQueuestd::deque多线程FIFO队列
SafeStackstd::deque多线程LIFO栈
SafeBlockQueuestd::queue生产者-消费者模式
SafeBlockQueueTrackingstd::queue任务追踪
SortedVectorstd::vector有序数据存储

4. SafeMap - 线程安全字典

4.1 概述

SafeMap 是 std::map 的线程安全封装,适合多线程环境下的键值对存储。

SafeMap<K,V>
-std::mutex mutex_
-std::map<K,V> map_
+Insert(key, value) : bool
+EnsureInsert(key, value)
+Find(key, value) : bool
+Erase(key)
+Clear()
+Size() : int
+IsEmpty() : bool
+Iterate(callback)
+ReadVal(key) : V
+FindOldAndSetNew(key, oldVal, newVal) : bool

4.2 核心方法详解

插入操作
SafeMap<int, std::string> userMap;

// 方式1: Insert - 如果key已存在则失败
bool success = userMap.Insert(1, "Alice");  // true
bool fail = userMap.Insert(1, "Bob");       // false,key=1已存在

// 方式2: EnsureInsert - 强制插入(覆盖已有值)
userMap.EnsureInsert(1, "Bob");  // 现在 map[1] = "Bob"
EnsureInsert方法
key存在?
EnsureInsert key,value
删除旧值
插入新值
直接插入
Insert方法
key存在?
Insert key,value
返回 false
插入成功
返回 true
查找操作
SafeMap<int, std::string> userMap;
userMap.Insert(1, "Alice");

// 方式1: Find - 安全查找
std::string name;
if (userMap.Find(1, name)) {
    std::cout << "找到: " << name << std::endl;  // 找到: Alice
}

// 方式2: ReadVal - 直接读取(不存在则返回默认值)
std::string name2 = userMap.ReadVal(1);  // "Alice"
std::string name3 = userMap.ReadVal(999); // "" (空字符串,默认值)
遍历操作
SafeMap<int, std::string> userMap;
userMap.Insert(1, "Alice");
userMap.Insert(2, "Bob");
userMap.Insert(3, "Charlie");

// 使用 Iterate 安全遍历
userMap.Iterate([](const int key, std::string& value) {
    std::cout << key << ": " << value << std::endl;
    // 可以在回调中修改 value
});
Lambda 修改值
SafeMap<int, int> counterMap;
counterMap.Insert(1, 0);

// 使用 Lambda 原子性地修改值
userMap.ChangeValueByLambda(1, [](int& count) {
    count++;  // 安全地递增
});

4.3 完整示例

#include "safe_map.h"
#include <thread>
#include <iostream>

using namespace OHOS;

void SafeMapDemo() {
    SafeMap<std::string, int> scoreMap;
    
    // 线程1: 添加数据
    std::thread t1([&scoreMap]() {
        for (int i = 0; i < 100; i++) {
            scoreMap.Insert("player" + std::to_string(i), i * 10);
        }
    });
    
    // 线程2: 读取数据
    std::thread t2([&scoreMap]() {
        for (int i = 0; i < 100; i++) {
            int score;
            if (scoreMap.Find("player" + std::to_string(i), score)) {
                std::cout << "玩家" << i << "分数: " << score << std::endl;
            }
        }
    });
    
    t1.join();
    t2.join();
    
    std::cout << "总玩家数: " << scoreMap.Size() << std::endl;
}

5. SafeQueue - 线程安全队列

5.1 概述

SafeQueue 实现了 FIFO(先进先出) 的线程安全队列。

SafeQueue FIFO
元素1
Push 入队
元素2
元素3
Pop 出队

5.2 类结构

继承
继承
«abstract»
SafeQueueInner<T>
#std::deque<T> deque_
#std::mutex mutex_
+Push(T)
+Pop(T) : bool
+Empty() : bool
+Size() : int
+Clear()
+Erase(T)
#DoPush(T) : void
#DoPop(T) : bool
SafeQueue<T>
#DoPush(T) : void
#DoPop(T) : bool
SafeStack<T>
#DoPush(T) : void
#DoPop(T) : bool

5.3 使用示例

#include "safe_queue.h"
using namespace OHOS;

void SafeQueueDemo() {
    SafeQueue<int> queue;
    
    // 入队
    queue.Push(1);
    queue.Push(2);
    queue.Push(3);
    
    std::cout << "队列大小: " << queue.Size() << std::endl;  // 3
    
    // 出队 (FIFO: 先进先出)
    int value;
    while (queue.Pop(value)) {
        std::cout << "出队: " << value << std::endl;
    }
    // 输出: 1, 2, 3
}

6. SafeStack - 线程安全栈

6.1 概述

SafeStack 实现了 LIFO(后进先出) 的线程安全栈。

flowchart TB
    subgraph SafeStack LIFO
        direction TB
        Push[Push 入栈] --> Q3[元素3 ← 栈顶]
        Q3 --> Q2[元素2]
        Q2 --> Q1[元素1]
        Q3 -.-> Pop[Pop 出栈]
    end
    
    style Push fill:#4caf50,color:#fff
    style Pop fill:#f44336,color:#fff

6.2 使用示例

#include "safe_queue.h"
using namespace OHOS;

void SafeStackDemo() {
    SafeStack<int> stack;
    
    // 入栈
    stack.Push(1);
    stack.Push(2);
    stack.Push(3);
    
    // 出栈 (LIFO: 后进先出)
    int value;
    while (stack.Pop(value)) {
        std::cout << "出栈: " << value << std::endl;
    }
    // 输出: 3, 2, 1
}

6.3 SafeQueue vs SafeStack 对比

flowchart LR
    subgraph SafeQueue
        direction LR
        A1[Push 1] --> A2[Push 2] --> A3[Push 3]
        A3 --> A4[Pop → 1, 2, 3]
    end
    
    subgraph SafeStack
        direction LR
        B1[Push 1] --> B2[Push 2] --> B3[Push 3]
        B3 --> B4[Pop → 3, 2, 1]
    end

7. SafeBlockQueue - 阻塞队列

7.1 概述

SafeBlockQueue 是一个有界阻塞队列,核心特性:

  • 🔒 线程安全:内部自动加锁
  • ⏸️ 阻塞等待:队列满时 Push 会等待,队列空时 Pop 会等待
  • 📏 容量限制:创建时指定最大容量
阻塞队列工作原理
Push
Pop
队列满?
生产者线程
阻塞等待
添加元素
通知消费者
消费者线程
队列空?
阻塞等待
取出元素
通知生产者

7.2 类结构

SafeBlockQueue<T>
#unsigned long maxSize_
#std::mutex mutexLock_
#std::condition_variable cvNotEmpty_
#std::condition_variable cvNotFull_
#std::queue<T> queueT_
+SafeBlockQueue(capacity)
+Push(elem) : void
+Pop() : T
+PushNoWait(elem) : bool
+PopNotWait(elem) : bool
+Size() : unsigned int
+IsEmpty() : bool
+IsFull() : bool

7.3 阻塞 vs 非阻塞方法

方法队列满时队列空时返回值
Push(elem)阻塞等待-void
Pop()-阻塞等待T
PushNoWait(elem)返回 false-bool
PopNotWait(elem)-返回 falsebool

7.4 生产者-消费者模式示例

#include "safe_block_queue.h"
#include <thread>
#include <iostream>

using namespace OHOS;

void ProducerConsumerDemo() {
    SafeBlockQueue<int> taskQueue(5);  // 容量为5
    
    // 生产者线程
    std::thread producer([&taskQueue]() {
        for (int i = 1; i <= 10; i++) {
            std::cout << "生产任务: " << i << std::endl;
            taskQueue.Push(i);  // 队列满时会阻塞
        }
    });
    
    // 消费者线程
    std::thread consumer([&taskQueue]() {
        for (int i = 0; i < 10; i++) {
            int task = taskQueue.Pop();  // 队列空时会阻塞
            std::cout << "消费任务: " << task << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        }
    });
    
    producer.join();
    consumer.join();
}

7.5 工作流程图

生产者 SafeBlockQueue(容量=2) 消费者 队列: [] Push(1) 队列: [1] Push(2) 队列: [1, 2] (满) Push(3) 阻塞等待...⏳ Pop() 队列: [2] 返回 1 唤醒生产者 继续执行 Push(3) 队列: [2, 3] 生产者 SafeBlockQueue(容量=2) 消费者

8. SafeBlockQueueTracking - 可追踪阻塞队列

8.1 概述

SafeBlockQueueTracking 在 SafeBlockQueue 基础上增加了任务追踪功能:

  • 📊 追踪未完成任务数
  • ⏱️ Join 等待所有任务完成
继承
SafeBlockQueue<T>
+Push(elem)
+Pop() : T
+PushNoWait(elem) : bool
SafeBlockQueueTracking<T>
-std::atomic<int> unfinishedTaskCount_
-std::condition_variable cvAllTasksDone_
+Push(elem)
+PushNoWait(elem) : bool
+OneTaskDone() : bool
+Join()
+GetUnfinishTaskNum() : int

8.2 核心方法

方法说明
Push(elem)入队,未完成任务数 +1
OneTaskDone()标记一个任务完成,未完成数 -1
Join()阻塞等待,直到所有任务完成
GetUnfinishTaskNum()获取未完成任务数

8.3 工作流程

主线程 SafeBlockQueueTracking 工作线程 Push(任务1) unfinishedTaskCount = 1 Push(任务2) unfinishedTaskCount = 2 Join() 阻塞等待...⏳ Pop() → 任务1 执行任务1 OneTaskDone() unfinishedTaskCount = 1 Pop() → 任务2 执行任务2 OneTaskDone() unfinishedTaskCount = 0 唤醒主线程 Join() 返回,继续执行 主线程 SafeBlockQueueTracking 工作线程

8.4 使用示例

#include "safe_block_queue.h"
#include <thread>
#include <iostream>
#include <functional>

using namespace OHOS;
using Task = std::function<void()>;

void TaskTrackingDemo() {
    SafeBlockQueueTracking<Task> taskQueue(10);
    
    // 添加任务
    for (int i = 1; i <= 5; i++) {
        taskQueue.Push([i]() {
            std::cout << "执行任务 " << i << std::endl;
            std::this_thread::sleep_for(std::chrono::milliseconds(100));
        });
    }
    
    std::cout << "未完成任务数: " << taskQueue.GetUnfinishTaskNum() << std::endl;  // 5
    
    // 工作线程
    std::thread worker([&taskQueue]() {
        Task task;
        while (taskQueue.PopNotWait(task)) {
            task();                    // 执行任务
            taskQueue.OneTaskDone();   // 标记完成
        }
    });
    
    // 等待所有任务完成
    taskQueue.Join();
    std::cout << "所有任务已完成!" << std::endl;
    
    worker.join();
}

9. SortedVector - 自动排序向量

9.1 概述

SortedVector 是一个自动保持有序的向量,插入元素时自动排序。

⚠️ 注意:SortedVector 不是线程安全的!如需多线程使用,请自行加锁。

SortedVector 自动排序
[5]
Add 5
Add 2
[2, 5]
Add 8
[2, 5, 8]
Add 3
[2, 3, 5, 8]

9.2 类结构

classDiagram
    class SortedVector~TYPE,AllowDuplicate~ {
        -std::vector~TYPE~ vec_
        +Add(item) ssize_t
        +IndexOf(item) ssize_t
        +OrderOf(item) size_t
        +Erase(index) iterator
        +Merge(vector) size_t
        +Clear()
        +Size() size_t
        +IsEmpty() bool
        +operator[](index) TYPE
        +Front() TYPE
        +Back() TYPE
        +Begin() iterator
        +End() iterator
    }
    
    note for SortedVector "AllowDuplicate 模板参数:\ntrue = 允许重复元素\nfalse = 不允许重复"

9.3 模板参数说明

// 允许重复元素 (默认)
SortedVector<int, true> vec1;   // 或 SortedVector<int>
vec1.Add(1);
vec1.Add(1);  // ✅ 允许,结果: [1, 1]

// 不允许重复元素
SortedVector<int, false> vec2;
vec2.Add(1);
vec2.Add(1);  // ❌ 失败,返回 -1

9.4 核心方法

Add - 添加元素(自动排序)
SortedVector<int> vec;
vec.Add(5);  // [5]
vec.Add(2);  // [2, 5]
vec.Add(8);  // [2, 5, 8]
vec.Add(3);  // [2, 3, 5, 8]

// 返回插入位置的索引
ssize_t index = vec.Add(6);  // 返回 3,结果: [2, 3, 5, 6, 8]
IndexOf - 查找元素
SortedVector<int> vec;
vec.Add(10);
vec.Add(20);
vec.Add(30);

ssize_t idx1 = vec.IndexOf(20);  // 返回 1
ssize_t idx2 = vec.IndexOf(99);  // 返回 -1 (NOT_FOUND)
OrderOf - 获取应插入位置
SortedVector<int> vec;  // [10, 20, 30]
vec.Add(10);
vec.Add(20);
vec.Add(30);

size_t pos = vec.OrderOf(25);  // 返回 2 (应该插入到索引2的位置)
Merge - 合并向量
SortedVector<int> vec1;
vec1.Add(1);
vec1.Add(3);
vec1.Add(5);  // [1, 3, 5]

std::vector<int> vec2 = {2, 4, 6};

vec1.Merge(vec2);  // [1, 2, 3, 4, 5, 6]

9.5 完整示例

#include "sorted_vector.h"
#include <iostream>

using namespace OHOS;

void SortedVectorDemo() {
    // 创建不允许重复的有序向量
    SortedVector<int, false> scores;
    
    // 添加分数
    scores.Add(85);
    scores.Add(92);
    scores.Add(78);
    scores.Add(92);  // 重复,添加失败
    scores.Add(88);
    
    std::cout << "分数排名:" << std::endl;
    for (size_t i = 0; i < scores.Size(); i++) {
        std::cout << i + 1 << ". " << scores[i] << std::endl;
    }
    // 输出:
    // 1. 78
    // 2. 85
    // 3. 88
    // 4. 92
    
    // 查找分数
    ssize_t idx = scores.IndexOf(88);
    if (idx != SortedVector<int, false>::NOT_FOUND) {
        std::cout << "88分排名第 " << idx + 1 << " 位" << std::endl;
    }
    
    // 获取最高分和最低分
    std::cout << "最低分: " << scores.Front() << std::endl;  // 78
    std::cout << "最高分: " << scores.Back() << std::endl;   // 92
}

9.6 二分查找原理

SortedVector 使用二分查找实现高效的查找和插入:

二分查找 IndexOf
中间值 = 40
查找 50 在 10,20,30,40,50,60,70
50 > 40?
在右半部分找
中间值 = 60
50 > 60?
在左半部分找
找到 50!

时间复杂度

操作复杂度
AddO(n) - 需要移动元素
IndexOfO(log n) - 二分查找
OrderOfO(log n) - 二分查找
operator[]O(1) - 直接访问

10. 使用场景与最佳实践

10.1 选择指南

FIFO
LIFO
需要什么容器?
需要键值对?
SafeMap
需要阻塞?
需要追踪任务?
SafeBlockQueueTracking
SafeBlockQueue
需要排序?
SortedVector
需自行加锁
FIFO还是LIFO?
SafeQueue
SafeStack

10.2 典型使用场景

容器典型场景
SafeMap缓存系统、会话管理、配置存储
SafeQueue消息队列、事件分发、日志收集
SafeStack撤销/重做功能、表达式求值
SafeBlockQueue线程池任务队列、生产者-消费者模式
SafeBlockQueueTracking批量任务处理、等待所有任务完成
SortedVector排行榜、有序数据检索

10.3 最佳实践

✅ 推荐做法
// 1. 使用 Find 而不是 ReadVal 检查元素是否存在
SafeMap<int, std::string> map;
std::string value;
if (map.Find(key, value)) {
    // key 存在,使用 value
}

// 2. 使用 PopNotWait 进行非阻塞检查
SafeBlockQueue<Task> queue(100);
Task task;
if (queue.PopNotWait(task)) {
    // 有任务,处理
} else {
    // 队列空,做其他事
}

// 3. 使用 EnsureInsert 确保值被更新
map.EnsureInsert(key, newValue);  // 不管key是否存在都会成功
❌ 避免的错误
// 错误1: 在循环中频繁调用 Size()
// ❌ 每次调用都要加锁
for (int i = 0; i < map.Size(); i++) { ... }

// ✅ 改用 Iterate
map.Iterate([](const K& key, V& value) { ... });

// 错误2: 忘记调用 OneTaskDone
SafeBlockQueueTracking<Task> queue(10);
Task task = queue.Pop();
task();
// ❌ 忘记调用 OneTaskDone(),Join() 会永远等待!

// ✅ 正确做法
task();
queue.OneTaskDone();

// 错误3: 在多线程中使用 SortedVector 不加锁
// SortedVector 不是线程安全的!
std::mutex mtx;
SortedVector<int> vec;
{
    std::lock_guard<std::mutex> lock(mtx);
    vec.Add(value);  // ✅ 手动加锁
}

10.4 性能对比

xychart-beta
    title "各容器操作时间复杂度"
    x-axis ["SafeMap", "SafeQueue", "SafeBlockQueue", "SortedVector"]
    y-axis "时间复杂度 (1=O(1), 2=O(log n), 3=O(n))" 1 --> 3
    bar [2, 1, 1, 3]
容器插入查找删除
SafeMapO(log n)O(log n)O(log n)
SafeQueueO(1)O(n)O(n)
SafeBlockQueueO(1)-O(1)
SortedVectorO(n)O(log n)O(n)

📊 API 速查表

SafeMap

方法说明返回值
Insert(key, value)插入(key存在则失败)bool
EnsureInsert(key, value)强制插入(覆盖)void
Find(key, value)查找bool
ReadVal(key)读取值V
Erase(key)删除void
Clear()清空void
Size()大小int
IsEmpty()是否为空bool
Iterate(callback)遍历void
FindOldAndSetNew(key, old, new)查找并替换bool

SafeQueue / SafeStack

方法说明返回值
Push(elem)入队/入栈void
Pop(elem)出队/出栈bool
Empty()是否为空bool
Size()大小int
Clear()清空void
Erase(elem)删除指定元素void

SafeBlockQueue

方法说明阻塞
Push(elem)入队✅ 队列满时阻塞
Pop()出队✅ 队列空时阻塞
PushNoWait(elem)非阻塞入队
PopNotWait(elem)非阻塞出队
Size()大小-
IsEmpty()是否为空-
IsFull()是否已满-

SortedVector

方法说明返回值
Add(item)添加(自动排序)ssize_t (索引)
IndexOf(item)查找索引ssize_t
OrderOf(item)获取应插入位置size_t
Erase(index)删除iterator
Merge(vector)合并size_t
operator[](index)访问元素TYPE&
Front() / Back()首/尾元素TYPE&

🎯 总结

mindmap
  root((安全容器))
    线程安全
      SafeMap
      SafeQueue
      SafeStack
      SafeBlockQueue
      SafeBlockQueueTracking
    非线程安全
      SortedVector
    阻塞支持
      SafeBlockQueue
      SafeBlockQueueTracking
    任务追踪
      SafeBlockQueueTracking

记住这三点

  1. SafeMap/SafeQueue/SafeStack = 普通容器 + 自动加锁
  2. SafeBlockQueue = 生产者-消费者模式的最佳选择
  3. SortedVector 不是线程安全的,需要自己加锁!

更多推荐