C++/MFC/ATL 线程安全字符串 read/write

C++/MFC/ATL Thread-Safe String read/write

我有一个 MFC class 启动了线程,线程需要修改主 class 的 CString 成员。

我讨厌互斥锁,所以一定有更简单的方法来做到这一点。

我正在考虑使用 boost.org 库或 atl::atomic 或 shared_ptr 变量。

读写字符串并确保线程安全的最佳方法是什么?

class MyClass
{
   public:
      void        MyClass();
      static UINT MyThread(LPVOID pArg);
      CString     m_strInfo;
};

void MyClass::MyClass()
{
    AfxBeginThread(MyThread, this);
    CString strTmp=m_strInfo; // this may cause crash
}

UINT MyClass::MyThread(LPVOID pArg)
{
     MyClass pClass=(MyClass*)pArd;
     pClass->m_strInfo=_T("New Value"); // non thread-safe change
}

根据 MSDN shared_ptr 自动工作 https://msdn.microsoft.com/en-us/library/bb982026.aspx

那么这是更好的方法吗?

#include <memory>
class MyClass
{
   public:
      void        MyClass();
      static UINT MyThread(LPVOID pArg);
      std::shared_ptr<CString>    m_strInfo;  // ********
};

void MyClass::MyClass()
{
    AfxBeginThread(MyThread, this);
    CString strTmp=m_strInfo; // this may cause crash
}

UINT MyClass::MyThread(LPVOID pArg)
{
     MyClass pClass=(MyClass*)pArd;
     shared_ptr<CString> newValue(new CString());

     newValue->SetString(_T("New Value"));
     pClass->m_strInfo=newValue; // thread-safe change?
}

您可以实现某种无锁方式来实现这一点,但这取决于您如何使用 MyClass 和您的线程。如果您的线程正在处理一些数据并且在处理之后需要更新 MyClass,那么请考虑将您的字符串数据放入其他 class 例如:

struct StringData {
    CString     m_strInfo;
};

然后在您的 MyClass 中:

class MyClass
{
   public:
      void        MyClass();
      static UINT MyThread(LPVOID pArg);
      StringData*     m_pstrData;
      StringData*     m_pstrDataForThreads;
};

现在,这个想法是在你的 ie 中。您使用 m_pstrData 的主线程代码,但您需要使用原子来存储指向它的本地指针,即:

void MyClass::MyClass()
{
    AfxBeginThread(MyThread, this);

    StringData*  m_pstrDataTemp = ATOMIC_READ(m_pstrData);
    if ( m_pstrDataTemp )
        CString strTmp=m_pstrDataTemp->m_strInfo; // this may NOT cause crash
}

一旦您的线程处理完数据,并想要更新字符串,您将自动分配 m_pstrDataForThreadsm_pstrData,并分配新的 m_pstrDataForThreads

问题在于如何安全删除 m_pstrData,我想你可以在这里使用 std::shared_ptr。

最后它看起来有点复杂,IMO 真的不值得付出努力,至少很难说这是否真的是线程安全的,当代码变得更复杂时 - 它仍然是线程安全的。这也是针对单工作线程的情况,你说你有多个线程。这就是为什么临界区是一个起点,如果它太慢然后考虑使用无锁方法。

顺便说一句。根据您更新字符串数据的频率,您还可以考虑使用 PostMessage 将指向新字符串的指针安全地传递给您的主线程。

[编辑]

ATOMIC_MACRO 不存在,它只是一个使其编译使用的占位符。 c++11 原子,示例如下:

#include <atomic>
...
std::atomic<uint64_t> sharedValue(0);
sharedValue.store(123, std::memory_order_relaxed);          // atomically store
uint64_t ret = sharedValue.load(std::memory_order_relaxed); // atomically read
std::cout << ret;

我会使用更简单的方法来保护变量 SetStrInfo:

void SetStrInfo(const CString& str)
{
    [Lock-here]
    m_strInfo = str;
    [Unlock-here]
}

为了锁定和解锁,我们可以使用 CCriticalSection(class 的成员),或者将其环绕在 CSingleLock RAII 周围。出于性能原因,我们也可能使用 slim-reader writer locks(用 RAII 包装 - 写一个简单的 class)。我们也可以为 RAII 使用更新的 C++ 技术 locking/unlocking.

叫我老派,但对我来说 std 命名空间有复杂的选项集 - 并不适合所有情况和所有人。