面试题答案
一键面试风险分析
- 数据竞争风险:多个线程同时读写该对象,可能导致数据不一致,例如一个线程读取到的数据是另一个线程修改到一半的状态。
- 内存释放风险:如果在函数返回引用后,其他线程意外释放了该对象的内存,调用函数的线程后续使用引用时会出现悬空指针,导致程序崩溃。
风险防范
- 使用互斥锁(Mutex):在访问共享对象的代码块前后加锁,保证同一时间只有一个线程能访问该对象。例如在C++ 中可以使用
std::mutex
:
std::mutex mtx;
SomeObject* GetSharedObject() {
std::lock_guard<std::mutex> lock(mtx);
static SomeObject obj;
return &obj;
}
- 读写锁(Read - Write Lock):如果读操作远多于写操作,可以使用读写锁。读操作时允许多个线程同时进行,写操作时则独占对象。如在C++ 中可使用
std::shared_mutex
:
std::shared_mutex rwMutex;
SomeObject* GetSharedObject() {
std::shared_lock<std::shared_mutex> lock(rwMutex);
static SomeObject obj;
return &obj;
}
- 原子操作:对于简单类型的成员变量,可以使用原子类型来避免数据竞争。例如在C++ 中,
std::atomic<int>
可保证对int
类型变量的原子操作。
优化代码结构提高性能和安全性
- 减少锁的粒度:尽量缩小加锁的代码块范围,只对真正需要保护的共享数据访问部分加锁,避免不必要的性能开销。
- 线程局部存储(Thread - Local Storage, TLS):如果每个线程都需要一个独立的对象副本,可以使用线程局部存储。在C++ 中可以通过
thread_local
关键字实现:
thread_local SomeObject localObj;
SomeObject* GetSharedObject() {
return &localObj;
}
这样每个线程都有自己独立的 localObj
,避免了线程间的数据竞争。
3. 使用线程安全的数据结构:如 std::queue
在多线程环境下不安全,可使用线程安全的队列实现,例如基于无锁数据结构的队列,可提高并发性能。