C++ 锁定相互引用的资源对

C++ 锁定相互引用的资源对,c++,multithreading,concurrency,synchronization,locking,C++,Multithreading,Concurrency,Synchronization,Locking,考虑以下几点: // There are guys: class Guy { // Each guy can have a buddy: Guy* buddy; // When a guy has a buddy, he is his buddy's buddy, i.e: // assert(!buddy || buddy->buddy == this); public: // When guys are birthed into

考虑以下几点:

// There are guys:
class Guy {
    // Each guy can have a buddy:
    Guy* buddy; // When a guy has a buddy, he is his buddy's buddy, i.e:
                // assert(!buddy || buddy->buddy == this);

public:
    // When guys are birthed into the world they have no buddy:
    Guy()
        : buddy{}
    {}

    // But guys can befriend each other:
    friend void befriend(Guy& a, Guy& b) {
        // Except themselves:
        assert(&a != &b);

        // Their old buddies (if any), lose their buddies:
        if (a.buddy) { a.buddy->buddy = {}; }
        if (b.buddy) { b.buddy->buddy = {}; }

        a.buddy = &b;
        b.buddy = &a;
    }

    // When a guy moves around, he keeps track of his buddy
    // and lets his buddy keep track of him:
    friend void swap(Guy& a, Guy& b) {
        std::swap(a.buddy, b.buddy);
        if (a.buddy) { a.buddy->buddy = &a; }
        if (b.buddy) { b.buddy->buddy = &b; }
    }
    Guy(Guy&& guy)
        : Guy()
    {
        swap(*this, guy);
    }
    Guy& operator=(Guy guy) {
        swap(*this, guy);
        return *this;
    }

    // When a Guy dies, his buddy loses his buddy.
    ~Guy() {
        if (buddy) { buddy->buddy = {}; }
    }
};
到目前为止,一切都很好,但现在我希望在不同线程中使用伙伴时,这种方法也能起作用。没问题,让我们把
std::mutex
放在
Guy

class Guy {
    std::mutex mutex;

    // same as above...
};
现在我只需要在链接或取消链接这两个人之前锁定他们的互斥体

这就是我被难倒的地方。以下是失败的尝试(以析构函数为例):

  • 死锁:

    ~Guy() {
        std::unique_lock<std::mutex> lock{mutex};
        if (buddy) {
            std::unique_lock<std::mutex> buddyLock{buddy->mutex};
            buddy->buddy = {};
        }
    }
    
    不幸的是,要访问buddy的互斥锁,我们必须访问
    buddy
    ,此时它不受任何锁的保护,并且可能正在从另一个线程进行修改,这是一种竞争条件

  • 不可扩展:

    正确性可以通过全局互斥来实现:

    static std::mutex mutex;
    ~Guy() {
        std::unique_lock<std::mutex> lock{mutex};
        if (buddy) { buddy->buddy = {}; }
    }
    
    static std::mutex mutex;
    ~Guy(){
    std::unique_lock{mutex};
    如果(伙伴){buddy->buddy={};}
    }
    
    但出于性能和可伸缩性的原因,这是不可取的

  • 那么,如果没有全局锁,这可能吗?如何使用?

    使用本身不是竞争条件,也不存在死锁风险

    std::lock
    将使用无死锁算法获得两个锁。它们将是某种(未指明的)尝试和撤退的方法

    另一种方法是确定任意锁顺序,例如使用对象的物理地址

    您已经正确地排除了对象伙伴本身的可能性,因此没有尝试两次将相同的
    互斥锁
    锁定()的风险

    我说这本身不是一个竞争条件,因为代码将确保完整性,如果a有buddy b,那么b有buddy a代表所有a和b

    事实上,在与两个对象成为朋友后的某一刻,它们可能会被另一个线程取消朋友关系,这可能是您的意图或其他同步所要解决的问题

    还要注意的是,当你与新朋友的朋友交朋友或解除朋友关系时,你需要一次锁定所有对象。 这是两个“未来”朋友和他们现在的朋友(如果有的话)。 因此,您需要锁定2、3或4个互斥锁

    std::lock
    不幸的是,它不接受数组,但是有一个版本在boost中可以这样做,或者您需要手动解决它

    为了澄清,我正在阅读可能的析构函数作为模型的例子。所有相关成员都需要同步相同的锁(例如,
    befriend()
    swap()
    unfriend()
    ,如果需要的话)。实际上,锁定2、3或4的问题适用于
    befriend()
    成员

    此外,析构函数可能是最糟糕的例子,因为正如在注释中提到的,一个对象是可破坏的,但可能与另一个线程存在锁争用,这是不合逻辑的。在更广泛的程序中肯定需要存在一些同步,以使析构函数中的锁在某一点上是冗余的,这是不可能的

    事实上,确保
    Guy
    对象在销毁之前没有好友的设计似乎是一个好主意,也是一个在析构函数中检查
    assert(buddy==nullptr)
    的调试先决条件。遗憾的是,这不能作为运行时异常,因为在析构函数中抛出异常会导致程序终止(
    std::terminate()

    事实上,真正的挑战(可能取决于周围的节目)是如何在交朋友时解除朋友。这似乎需要一个试退循环:

  • 锁a和锁b
  • 看看他们有没有朋友
  • 如果他们已经是好朋友了,你就完蛋了
  • 如果他们有其他伙伴,解锁a&b,并锁定a和b及其伙伴(如果有)
  • 如果他们又去了,检查一下他们是否没有改变
  • 调整相关成员
  • 对于周围的程序来说,这是一个问题,它是否会有活锁风险,但任何尝试-退出方法都会有同样的风险

    不起作用的是
    std::lock()
    a&b,然后
    std::lock()
    buddies,因为这样会有死锁的风险

    所以要回答这个问题-是的,没有全局锁是可能的,但这取决于周围的程序。它可能在许多
    Guy
    对象的群体中,竞争很少见,而且活跃度很高。 但可能是因为有少量的对象被激烈争夺(可能是在大量人群中),导致了问题。如果不了解更广泛的应用,就无法对其进行评估

    解决这一问题的一种方法是锁升级,它实际上是一个全局锁。本质上,这意味着如果在重试循环中有太多的行程,那么将设置一个全局信号量,命令所有线程进入全局锁模式一段时间。一段时间可能是一系列操作或一段时间,或者直到全局锁上的争用平息


    因此,最后的答案是“是的,这是绝对可能的,除非它不起作用,在这种情况下‘不’”。

    我认为你需要将“佛性”和“男性”区分开来。i、 e.一个名为“Guys”的新类,其中包含对零个、一个或两个Guys的引用。@RichardHodges,这有什么帮助?同样的问题也存在于
    家伙和每个
    家伙之间。我有点同意@richardhoges。从概念上讲,似乎需要同步的是
    Guy
    s之间的关系,而不是
    Guy
    s本身。这可以通过
    家伙
    之间共享的互斥锁来实现,每当好友关系被切断时(无论是新的
    家伙
    成为朋友还是被摧毁),互斥锁都会被锁定。我非常喜欢这个挑战。我对“哲学家进餐”问题持批评态度,因为虽然它显示了死锁,但它不是典型系统的非常现实的模型,因为锁的数量与线程的数量相同,这是一个非典型问题。我想知道应用程序可能是什么。我所能想到的就是一套
    static std::mutex mutex;
    ~Guy() {
        std::unique_lock<std::mutex> lock{mutex};
        if (buddy) { buddy->buddy = {}; }
    }