使用相同的关键部分对象进行阅读和写作

Reading and writing using same critical section object

本文关键字:对象 键部      更新时间:2023-10-16

我需要编写一个从文件中读取和写入文件的类。当我进行写操作时,不应该进行读操作,反之亦然。我可以使用一个关键部分对象吗?像这样:

FileWorker.h

class FileWorker
{
public:
    FileWorker();
    void WriteIntoFile(const char* fileName, const char* stringToWrite);
    void ReadFromFile(const char* fileName, char* stringToRead, int* stringLength);
    ~FileWorker();
};

FileWorker.cpp

#include <windows.h>
#include "FileWorker.h"
static CRITICAL_SECTION g_criticalSection;
FileWorker::FileWorker()
{
#ifdef WIN32APP
    InitializeCriticalSection(&g_criticalSection);
#endif
}
void FileWorker::ReadFromFile(const char *fileName, char *stringToRead, int *stringLength)
{
    EnterCriticalSection(&g_criticalSection);
    // Do Read
    LeaveCriticalSection(&g_criticalSection);
}
void FileWorker::WriteIntoFile(const char *fileName, const char *stringToWrite)
{
    EnterCriticalSection(&g_criticalSection);
    // Do Write
    LeaveCriticalSection(&g_criticalSection);
}
FileWorker::~FileWorker()
{
#ifdef WIN32APP
    DeleteCriticalSection(&g_criticalSection);
#endif
}

谢谢。

如果你谈论的是同一个文件的读/写,那么你需要使用相同的关键部分(就像你目前所做的那样),否则一个线程可能正在读取该文件,而另一个线程正在向其写入,这正是你使用关键部分要避免的。

但是,按照FileWorker类当前的编写方式,您可以读取/写入任意文件,但只有一个全局关键部分。这在这种情况下仍然有效,但如果很少有对同一文件的争用,最终会增加额外的开销。这对您来说可能是一个可以接受的折衷方案,这取决于您的使用模式以及此代码的时间关键性

此外,正如Begemoth所指出的,如果创建两个使用寿命重叠的FileWorker,那么单个全局关键部分就会出现问题。您将需要类似于建议的内容,以确保您不会尝试初始化已初始化的关键部分,或删除已删除的关键部分。

正如其他答案所指出的,当同时使用FileWorker的多个实例时,单个全局关键部分会导致问题。您应该使关键部分成为FileWorker的成员,并在构造函数/析构函数中初始化/删除它。

对于锁定的实现,我建议编写一个小的助手类来支持作用域锁定:

class ScopedCriticalSection {
public:
    ScopedCriticalSection(CRITICAL_SECTION & criticalSection)
      : m_criticalSection(criticalSection)
    {
        EnterCriticalSection(&m_criticalSection);
    }
    ~ScopedCriticalSection() {
        LeaveCriticalSection(&m_criticalSection);
    }
private:
    CRITICAL_SECTION & m_criticalSection;
}

你可以这样使用这个对象:

void FileWorker::ReadFromFile(const char *fileName, char *stringToRead, 
                              int *stringLength)
{
    ScopedCriticalSection guard(m_criticalSection); // enters the cs
    // read stuff
} // the critical section is left when the guard is destroyed

要了解这是如何工作的,请阅读RAII。

您需要所有线程使用相同的关键部分来保护共享资源。除了构造函数和析构函数外,代码都可以。此代码导致未定义的行为:

FileWorker a;
FileWorker b;

因为CCD_ 5被初始化两次而没有中间删除。您需要添加静态成员函数来初始化和完成类。

static void FileWorker::initialize()
{
  InitializeCriticalSection(&g_criticalSection);
}
static void FileWorker::finialize()
{
  DeleteCriticalSection(&g_criticalSection);
}

当您需要在每个文件的基础上进行同步时,正确的方法是在文件I/O手段(HANDLEs、file*s、std::fstream等)之上实现抽象。例如

class SyncronizedOutStream
{
  CRITICAL_SECTION cs;
  std::ofstream ostm;
  SyncronizedOutStream(const SyncronizedOutStream&);
  void operator= (const SyncronizedOutStream&);
public:
  explicit SyncronizedOutStream(const std::string& filename) 
    : ostm(filename.c_str())
  {
    InitializeCriticalSection(&cs);
  }
  ~SyncronizedOutStream()
  {
    DeleteCriticalSection(&cs);
  }
  template<typename T>
  SyncronizedOutStream& operator<< (T x)
  {
     ostm << x;
     return *this;
  }
  void lock()
  {
    EnterCriticalSection(&cs);
  }
  void release()
  {
    LeaveCriticalSection(&cs);
  }
};

此类的实例不可复制,也不可赋值,这一点很重要,因为必须复制关键节。