是否需要互斥+原子来使这段代码线程安全,或者是互斥是否足够?

时间:2011-06-30 00:12:37

标签: locking c++11 mutex

我有些疑问,互斥锁足以确保以下代码示例的线程安全性,或者是否需要原子。简而言之,问题是:使idxActive成为常规int会使此代码线程不安全吗?或者代码甚至与原子线程不安全? :( 如果它很重要,我就是32位x86,linux,gcc 4.6。当然我认为32或64位没有差异,但如果在32位和64位之间有任何差异,我想知道。

#include <memory>
#include <boost/thread/thread.hpp>
#include <string>
#include <vector>
#include <atomic>
#include <boost/thread/mutex.hpp>
using namespace std;
using namespace boost;
static const int N_DATA=2;
class Logger
{
    vector<string> data[N_DATA];
    atomic<int> idxActive;
    mutex addMutex;
    mutex printMutex;
public:
    Logger()
    {
        idxActive=0;
        for (auto& elem: data)
            elem.reserve(1024);
    }
private:
    void switchDataUsed()
    {
        mutex::scoped_lock sl(addMutex);
        idxActive.store( (idxActive.load()+1)%N_DATA );
    }
public:
    void addLog(const string& str)
    {
        mutex::scoped_lock sl(addMutex);
        data[idxActive.load()].push_back(str);
    }
    void printCurrent()
    {
        mutex::scoped_lock sl(printMutex);
        switchDataUsed();
        auto idxOld=(idxActive.load()+N_DATA-1)%N_DATA; //modulo -1
        for (auto& elem:data[idxOld])
            cout<<elem<<endl;
        data[idxOld].clear();
    }
};
int main()
{
    Logger log;
    log.addLog(string("Hi"));
    log.addLog(string("world"));
    log.printCurrent();
    log.addLog(string("Hi"));
    log.addLog(string("again"));
    log.printCurrent();
    return 0;
}

2 个答案:

答案 0 :(得分:3)

如果对这些变量的所有访问都受互斥锁保护,则不需要使用原子变量。在您的代码中就是这种情况,因为所有公共成员函数在进入时都会锁定addMutex。因此addIndex可以是普通的int,一切都会正常。互斥锁定和解锁可确保正确的值以正确的顺序对其他线程可见。

std::atomic<>允许并发访问外部保护互斥锁,确保线程看到变量的正确值,即使面对并发修改也是如此。如果您坚持默认的内存排序,它还可以确保每个线程读取变量的最新值。 std::atomic<>可用于编写没有互斥锁的线程安全算法,但如果所有访问都受相同的互斥锁保护,则不需要{<1}}。

重要更新

我刚注意到您使用的是两个互斥锁:一个用于addLog,另一个用于printCurrent。在这种情况下,执行需要idxActive是原子的,因为单独的互斥锁不会在它们之间提供任何同步

答案 1 :(得分:1)

atomic与线程安全没有直接关系。它只是确保对它的操作正是它所说的:原子。即使您的所有操作都是原子操作,您的代码也不一定是线程安全的。

在您的情况下,代码应该是安全的。一次只能有一个帖子进入printCurrent()。执行此功能时,其他线程可以调用addLog()(但一次也只能调用1个)。根据{{​​1}}是否已经执行,这些条目将进入当前日志,否则它们将不会,但迭代它时将不会输入任何条目。一次只有一个线程可以输入switchCurrent,它与addLog共享其互斥锁,因此无法同时执行它们。

即使你使switchCurrent成为一个简单的int Mh也是如此,C ++内存模型只处理单线程代码 - 所以我不太确定理论上是否它可以打破它。我认为如果你使idxActive易变(基本上不允许对其进行任何加载/存储优化),那么它可以用于所有实际目的。或者,您可以从idxActive中删除互斥锁,但是您需要保持switchCurrent原子。

作为改进,我会让idxActive返回旧索引,而不是重新计算它。