面试题答案
一键面试确保数据共享正确性的方法及同步机制
- 互斥锁(Mutex)
- 原理:互斥锁用于保护共享资源,同一时间只有一个线程可以获取锁,从而访问共享数据。获取锁的线程执行完对共享数据的操作后,释放锁,其他线程才有机会获取锁并访问数据。
- 示例代码:
#include <iostream>
#include <mutex>
#include <thread>
std::mutex mtx;
int sharedData = 0;
void increment() {
for (int i = 0; i < 10000; ++i) {
mtx.lock();
sharedData++;
mtx.unlock();
}
}
int main() {
std::thread t1(increment);
std::thread t2(increment);
t1.join();
t2.join();
std::cout << "Final value of sharedData: " << sharedData << std::endl;
return 0;
}
- 条件变量(Condition Variable)
- 原理:条件变量通常与互斥锁一起使用,用于线程间的同步通信。一个线程可以在条件变量上等待,直到另一个线程通知该条件变量,此时等待的线程被唤醒。
- 示例代码:
#include <iostream>
#include <mutex>
#include <condition_variable>
#include <thread>
std::mutex mtx;
std::condition_variable cv;
bool ready = false;
void print_id(int id) {
std::unique_lock<std::mutex> lock(mtx);
while (!ready) cv.wait(lock);
std::cout << "thread " << id << '\n';
}
void go() {
std::unique_lock<std::mutex> lock(mtx);
ready = true;
cv.notify_all();
}
int main() {
std::thread threads[10];
for (int i = 0; i < 10; ++i)
threads[i] = std::thread(print_id, i);
std::cout << "10 threads ready to race...\n";
go();
for (auto& th : threads) th.join();
return 0;
}
- 原子操作(Atomic Operations)
- 原理:原子操作是不可分割的操作,在执行过程中不会被其他线程打断。对于一些简单的数据类型(如整数、指针等),使用原子操作可以避免使用锁带来的开销。
- 示例代码:
#include <iostream>
#include <atomic>
#include <thread>
std::atomic<int> sharedAtomicData(0);
void atomicIncrement() {
for (int i = 0; i < 10000; ++i) {
sharedAtomicData++;
}
}
int main() {
std::thread t1(atomicIncrement);
std::thread t2(atomicIncrement);
t1.join();
t2.join();
std::cout << "Final value of sharedAtomicData: " << sharedAtomicData << std::endl;
return 0;
}
同步机制的优化
- 减少锁的粒度
- 方法:将大的锁保护区域划分成多个小的锁保护区域,使得不同线程可以同时访问不同的共享资源,从而提高并发性能。
- 示例:假设一个类有多个成员变量,原来使用一个大锁保护所有成员变量的访问,优化后可以为不同成员变量或相关成员变量组分别设置锁。
class SharedResources {
public:
std::mutex mtx1;
std::mutex mtx2;
int data1;
int data2;
void updateData1(int value) {
std::lock_guard<std::mutex> lock(mtx1);
data1 = value;
}
void updateData2(int value) {
std::lock_guard<std::mutex> lock(mtx2);
data2 = value;
}
};
- 使用读写锁(Read - Write Lock)
- 方法:如果共享数据的读操作远多于写操作,可以使用读写锁。读写锁允许多个线程同时进行读操作,但只允许一个线程进行写操作。当有写操作时,读操作会被阻塞。
- 示例代码:
#include <iostream>
#include <shared_mutex>
#include <thread>
std::shared_mutex rwMutex;
int sharedReadWriteData = 0;
void readData() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
std::cout << "Read data: " << sharedReadWriteData << std::endl;
}
void writeData(int value) {
std::unique_lock<std::shared_mutex> lock(rwMutex);
sharedReadWriteData = value;
std::cout << "Write data: " << sharedReadWriteData << std::endl;
}
int main() {
std::thread readThread1(readData);
std::thread readThread2(readData);
std::thread writeThread(writeData, 42);
readThread1.join();
readThread2.join();
writeThread.join();
return 0;
}
- 无锁数据结构
- 方法:使用无锁数据结构,如无锁队列、无锁链表等。这些数据结构通过原子操作和其他技巧实现线程安全,避免了锁带来的开销。
- 示例:虽然C++标准库没有提供通用的无锁队列,但可以自己实现或使用第三方库(如Intel TBB库中的无锁队列)。例如,自己实现一个简单的无锁队列可以参考以下伪代码思路:
template <typename T>
class LockFreeQueue {
private:
struct Node {
T data;
std::atomic<Node*> next;
Node(const T& value) : data(value), next(nullptr) {}
};
std::atomic<Node*> head;
std::atomic<Node*> tail;
public:
LockFreeQueue() : head(new Node(T())), tail(head.load()) {}
void enqueue(const T& value) {
Node* newNode = new Node(value);
Node* oldTail;
do {
oldTail = tail.load();
} while (!tail.compare_exchange_weak(oldTail, newNode));
oldTail->next.store(newNode);
}
bool dequeue(T& value) {
Node* oldHead;
Node* next;
do {
oldHead = head.load();
if (oldHead == tail.load()) return false;
next = oldHead->next.load();
} while (!head.compare_exchange_weak(oldHead, next));
value = next->data;
delete oldHead;
return true;
}
};
通过合理选择和优化同步机制,可以有效提高多线程程序在对象间数据共享时的性能。