如果已经通过互斥锁保护,我是否需要使用原子<>

Xin*_*ang 8 c++ multithreading c++11

鉴于该代码这个岗位,实行Semaphore只使用atomic<>mutex.

我只是好奇,既然count已经被警卫了updateMutex,是atomic<>必要的吗?

struct Semaphore {
    int size;
    atomic<int> count;
    mutex updateMutex;

    Semaphore(int n) : size(n) { count.store(0); }

    void aquire() {
        while (1) {
            while (count >= size) {}
            updateMutex.lock();
            if (count >= size) {
                updateMutex.unlock();
                continue;
            }
            ++count;
            updateMutex.unlock();
            break;
        }
    }

    void release() {
        updateMutex.lock();
        if (count > 0) {
            --count;
        } // else log err
        updateMutex.unlock();
    }
};
Run Code Online (Sandbox Code Playgroud)

没有atomic,我认为构造函数会出现同步问题.如果其他线程在构造后立即使用它,则可能无法看到计数分配.

如果是这样,怎么样size?它是否也需要受到保护atomic<>

或者atomic<>是因为这两个完全无用的sizecount将可见无论何时其他线程使用它们.

谢谢!

Dan*_*rey 7

我认为真正的原因count是一个atomic<int>是在aquire() 外面的的mutex在这条线-保护区:

while (count >= size) {}
Run Code Online (Sandbox Code Playgroud)

如果没有atomic,则允许编译假定读取一次就足够了,并且不会轮询其他线程的更改值.


Die*_*ühl 6

提出了多个问题.所有都要求理解基础概念:如果一个对象由至少一个由另一个线程访问(读取或写入)并且写入和访问不同步的线程写入,则会有数据竞争.数据竞赛的正式定义见1.10 [intro.multithread]第21段:

程序的执行包含数据竞争,如果它在不同的线程中包含两个冲突的动作,其中至少有一个不是原子的,并且都不会在另一个之前发生.[...]

包含数据竞争的程序具有未定义的行为,即程序需要确保它是免费的数据竞争.现在回答不同的问题:

  1. 是否有必要在构造函数中使用同步?

    它取决于对象是否可以由不同的线程同时访问,同时它仍在构建中.我可以想象并发访问正在构建的对象的唯一情况是在静态初始化期间,其中多个线程已经开始访问共享对象.由于对全局对象的构造顺序的弱约束,我无法想象无论如何都将使用全局对象并且static实现同步函数本地对象的构造.否则,我希望使用适当同步的机制跨线程共享对象的引用.也就是说,我会设计系统,使构造函数不需要同步.

  2. 已经锁定了.这是否意味着count不必是原子的.

    由于count在获取acquire()锁之前在函数中访问,因此它将是对另一个线程写入的对象的非同步访问,即,您将具有数据争用,因此,未定义的行为.本count必须是原子的.

  3. 是否有必要size同步.

    size成员仅在构造函数中被修改,Semaphore并且通过实际使其成为const成员来强制执行该成员是合理的.假设在构造期间不同时访问对象(参见上面的1.),访问时不存在数据竞争的可能性size.

请注意,您实际上不应该无人防守地使用互斥锁的成员lock()unlock()成员.相反,您应该使用std::lock_guard<std::mutex>std::unique_lock<std::mutex>可能使用辅助块.这两个类保证始终释放获取的锁.我还要问一个忙碌的等待信号量获取锁定是否是正确的方法.