使用原子而不是CriticalSection

Use atomic instead of CriticalSection?

本文关键字:CriticalSection      更新时间:2023-10-16

我有一段代码,它提供了一些数据,对文件进行一些操作等等。我用1个线程给出数据,用4个线程读取和搜索数据内部。

4线程使用文件矢量进行搜索(使用相同的函数)。为了避免同步问题(所有线程在同一时间读取同一文件),我使用CriticalSection()WinAPI:

void ReadData(char* fileName)
{
EnterCriticalSection(&CriticalSection);
// Open file
// Read file data
std::vector<std::string> data;
... Find data inside file
// Close file
LeaveCriticalSection(&CriticalSection);
}

但是我看到了这个帖子这个:

原子类型的对象是唯一没有数据竞赛的C++对象;也就是说,如果一个线程写入一个原子对象,而另一个线程从中读取,则行为是定义良好的。

我的问题是:使用std::atomic而不是CriticalSection更好?或者我错过了对原子学用法的理解。

使用标准C++

在您的代码中,您使用Microsoft的WinAPI的EnterCriticalSection()LeaveCriticalSection()函数。

这些都有wto的主要不便:首先它们不可移植,其次它们不安全:如果异常会导致线程以意外的方式离开ReadData(),会发生什么?你可能会得到一个关键的部分,它对窗口来说是不可离开的,让所有其他线程都饿死了!

如Werner所展示的,在mutex上使用lock_guard的标准C++替代方案要安全得多:首先,它可以跨平台移植,但此外,它实现了RAII习惯用法,该习惯用法确保如果出现意外异常,lock_guard在函数保留时会被破坏,从而导致mutex被释放。

使用原子可能不够

通常,人们倾向于使用原子论,因为它们避免了数据竞争,并给人一种它将解决所有线程同步问题的印象。

不幸的是,事实并非如此。一旦你使用了几个原子,你可能会对整体一致性做出假设,而在现实中,事情可能会发生不同的情况,并导致非常严重的错误。创建无锁算法和数据结构极具挑战性和难度。因此,我强烈建议大家阅读安东尼·威廉姆斯的优秀著作《;C++并发操作":他对所有相关方面进行了深入的探讨。

其他备选方案

在你的问题中,你提到了一个向量。维护并发线程安全向量是非常困难的,因为基本上,每当必须扩展向量的容量时,都可能发生重新分配,从而使指向该向量的所有迭代器和指针无效,无论它们在哪里使用。幸运的是,有一些线程安全的实现,比如微软的并行模式库。

另一方面,如果您只使用向量来存储文件的行并按顺序处理它们,那么您还可以考虑使用队列,这样可以使用许多可用的线程安全实现之一,例如boost。

std::atomic不会对矢量过载。它也不可复制或移动(从这里到这里),因此不能将其用作std::vector value_type(或者可能需要提供您自己的专业化,我没有(也不会)尝试/ed)。

然而,如果你想让你的代码独立于平台,std::mutex会做得很好,例如:

std::mutex myMutex_; //Typically a member or static or in unnamed namespace
void ReadData(char* fileName)
{
std::lock_guard<std::mutex> guard(myMutex_);
// Open file
// Read file data
std::vector<std::string> data;
... Find data inside file
// Close file
//... Releases when scoped left...
}

从某种意义上说,我的回答与上一篇文章相呼应。