面试题答案
一键面试原子操作(std::atomic
)
- 优点:
- 性能高:原子操作通常由硬件指令实现,开销小,在对单个变量进行简单读写操作时,性能优于锁机制,特别适合高并发场景。
- 无死锁风险:因为不需要获取和释放锁,不存在死锁问题。
- 缺点:
- 功能局限:只能对单个变量(或简单数据类型的聚合体)进行原子操作,对于复杂数据结构(如链表、哈希表),原子操作无法直接保证整体的一致性和完整性。
- 编程复杂:需要精细控制,例如在实现复杂逻辑时,需要处理内存顺序等问题,增加编程难度。
锁机制(std::mutex
及其衍生类型)
- 优点:
- 功能强大:能够保护任意复杂的数据结构和代码块,通过锁的获取和释放,保证数据在临界区的一致性和完整性。
- 易于理解和使用:编程模型简单直观,对于开发人员来说易于上手。
- 缺点:
- 性能开销:锁的获取和释放操作涉及系统调用,开销较大,在高并发场景下可能成为性能瓶颈。
- 死锁风险:如果锁的使用不当,容易出现死锁情况,排查和解决死锁问题较为困难。
场景选择
- 简单变量操作:如果只是对简单数据类型(如整数、指针)进行计数、标志位设置等简单操作,优先使用原子操作,以提高性能。
- 复杂数据结构操作:对于复杂数据结构(如链表、哈希表),需要保证数据整体一致性和完整性时,使用锁机制更为合适。
代码示例
#include <iostream>
#include <mutex>
#include <atomic>
#include <thread>
#include <vector>
// 复杂对象
class ComplexObject {
private:
std::atomic<int> count; // 可使用原子操作的简单成员
std::mutex mtx;
// 链表节点结构
struct ListNode {
int value;
ListNode* next;
ListNode(int val) : value(val), next(nullptr) {}
};
ListNode* head;
public:
ComplexObject() : count(0), head(nullptr) {}
// 使用原子操作增加计数
void incrementCount() {
++count;
}
// 使用锁机制操作链表
void addToList(int value) {
std::lock_guard<std::mutex> lock(mtx);
ListNode* newNode = new ListNode(value);
newNode->next = head;
head = newNode;
}
// 获取计数
int getCount() const {
return count.load();
}
// 打印链表
void printList() const {
std::lock_guard<std::mutex> lock(mtx);
ListNode* current = head;
while (current) {
std::cout << current->value << " ";
current = current->next;
}
std::cout << std::endl;
}
};
int main() {
ComplexObject obj;
std::vector<std::thread> threads;
// 启动多个线程操作对象
for (int i = 0; i < 10; ++i) {
threads.emplace_back([&obj]() {
obj.incrementCount();
obj.addToList(i);
});
}
// 等待所有线程完成
for (auto& th : threads) {
th.join();
}
// 输出结果
std::cout << "Count: " << obj.getCount() << std::endl;
std::cout << "List: ";
obj.printList();
return 0;
}
在上述代码中,count
成员变量使用 std::atomic
进行计数操作,利用了原子操作的高性能优势;而链表操作则使用 std::mutex
保护,确保链表结构在多线程环境下的一致性和完整性。这样结合使用原子操作和锁机制,既提高了性能,又保证了线程安全性。