在多线程编程中,数据的同步访问是一个常见的问题。如果多个线程同时访问同一个变量而没有适当的同步机制,可能会导致数据损坏。为了避免这种情况,通常会使用同步对象(如互斥锁)来确保在访问变量之前对其进行锁定,并在访问完成后解锁。然而,这种方法可能会导致一些难以发现的错误,例如忘记锁定同步对象或在变量使用后忘记解锁,这可能会导致数据损坏或线程阻塞。
为了避免这些问题,可以采用面向对象的方法,将同步对象与想要保护的数据绑定在一起,并将它们封装在一个线程安全的智能指针中。这样,就可以使用这个线程安全的智能指针来代替原始数据,从而确保数据的同步访问。
线程安全智能指针具有以下特点:
线程安全智能指针由两个类组成:
下面是一个使用线程安全智能指针重构的示例代码:
#include <string>
#include "ThreadPtr.hpp" // 线程安全智能指针的头文件
typedef CThreadPtr<std::string> TMyDataPtr;
class CMyClass {
public:
CMyClass() : m_data(new std::string) { ... }
TMyDataPtr m_data;
};
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
CMyClass* myClass = (CMyClass*)lpParameter;
myClass->m_data->assign("another thread");
return 0;
}
int main() {
CMyClass myClass;
HANDLE threadA = ::CreateThread( ..., ThreadProc, &myClass, ...);
myClass.m_data->assign("main thread");
::CloseHandle(threadA);
}
在这个示例中,不能忘记在访问m_data之前对其进行锁定。
事务锁允许在不让其他线程访问任何变量的情况下对多个变量进行更改。这通常需要保持这些变量之间的一致性。
事务锁可以通过使用CLocker(CThreadPtr的嵌套类)来实现,如下例所示:
typedef CThreadPtr<MyStructWithSeveralDataMembers> TMyDataPtr;
void MakeTransaction(TMyDataPtr& obj) {
TMyDataPtr::CLocker lock(obj);
obj->ChangeOneDataMember();
obj->ChangeAnotherDataMember();
}
在这个示例中,变量lock将在定义它的块退出时被销毁(互斥锁被释放),从而允许以线程安全的方式执行obj上的任何数量的操作。
要获取CThreadPtr指向的对象的数据引用,可以使用以下代码:
typedef CThreadPtr<std::string> TMyDataPtr;
TMyDataPtr ptr = new std::string;
TMyDataPtr::CLocker lock(ptr);
std::string& ref = **ptr;
ref = "new value";
要获取CThreadPtr指向的对象的数据指针,可以使用以下代码:
typedef CThreadPtr<std::string> TMyDataPtr;
TMyDataPtr ptr = new std::string;
TMyDataPtr::CLocker lock(ptr);
std::string* rawPtr = **ptr;
rawPtr->assign("new value");