2017-05-16 100 views
3

的考虑以下几点:锁定对相互引用资源

// 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::mutexGuy

class Guy { 
    std::mutex mutex; 

    // same as above... 
}; 

现在我只是有关联或取消关联他们两人之前锁定这两个家伙的互斥。

这是我难住的地方。下面是失败的尝试(使用析构函数为例):

  1. 僵局:

    ~Guy() { 
        std::unique_lock<std::mutex> lock{mutex}; 
        if (buddy) { 
         std::unique_lock<std::mutex> buddyLock{buddy->mutex}; 
         buddy->buddy = {}; 
        } 
    } 
    

    当两个哥们在大约在同一时间被破坏,这可能是他们每个人锁定自己互斥,然后试图锁定他们的伙伴的互斥体,从而导致僵局。

  2. 竞争条件:

    好了,所以我们只需要手动或使用std::lock锁定一致的顺序互斥:不幸的是

    ~Guy() { 
        std::unique_lock<std::mutex> lock{mutex, std::defer_lock}; 
        if (buddy) { 
         std::unique_lock<std::mutex> buddyLock{buddy->mutex, std::defer_lock}; 
         std::lock(lock, buddyLock); 
         buddy->buddy = {}; 
        } 
    } 
    

    ,去好友的互斥体,我们必须访问buddy哪些在这一点上不受任何锁保护,并且可能正在从另一个线程中修改,这是一种竞争条件。

  3. 不可扩展:

    正确性可以能获得具有全局mutex:

    static std::mutex mutex; 
    ~Guy() { 
        std::unique_lock<std::mutex> lock{mutex}; 
        if (buddy) { buddy->buddy = {}; } 
    } 
    

    但是,这是不可取的性能和可扩展性的原因。

那么这是可能做到没有全局锁?怎么样?

+0

我想你需要分开“好友”和“家伙”的关注。即一个名为“Guys”的新类,其中包含对零个,一个或两个家伙的引用。 –

+0

@RichardHodges如何帮助? “家伙”和每个“家伙”之间会存在同样的问题。 –

+0

我有点赞同@RichardHodges。从概念上讲,似乎需要同步的是“Guy”而非“Guy”本身之间的关系。这可以通过'Guy'之间共享的互斥体来完成,每当伙伴关系被切断时(无论是新朋友“朋友”结识还是被破坏),锁定都会被锁定。 – bnaecker

回答

2

使用std::lock不是竞赛条件(本身),也不会冒死锁的风险。

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

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

您已经正确排除了对象伙伴本身的可能性,因此不会尝试lock()相同的mutex两次。

我说这不是一个竞争条件本身,因为该代码将做的是确保完整性,如果a有好友b,然后b有伙计a所有a和b。

事实上,在结交两个对象之后,他们可能会被另一个线程解放出来的那一刻,大概是你打算或通过其他同步来解决的。

还要注意的是,当你和朋友交好朋友并且可能不友好时,你需要立即锁定所有的对象。 这是两个'成为'朋友和他们现在的朋友(如果有的话)。因此,您需要锁定2,3或4个互斥锁。

std::lock不幸的是并不需要一个数组,但有一个版本可以在boost中执行,或者您需要手动解决它。

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

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

确实保证Guy对象面前破坏没有好友设计似乎是一个好主意,在析构函数检查assert(buddy==nullptr)调试前状态。遗憾的是,不能作为运行时异常留下,因为在析构函数中抛出异常可能导致程序终止(std::terminate())。事实上,真正的挑战(可能取决于周围的计划)是如何在结交朋友时不友好。这似乎需要一个尝试撤退循环:

  1. 锁定a和b。
  2. 看看他们是否有好友。
  3. 如果他们已经是好友 - 你就完成了。
  4. 如果他们有其他好友,请解锁& b,然后锁定a和b以及他们的好友(如果有的话)。
  5. 检查好友没有改变,如果他们再次去。
  6. 调整相关成员。

这是一个周围的程序是否存在风险锁定的问题,但任何尝试撤销方法都面临同样的风险。

什么不会工作是std::lock() a & b然后std::lock()的好友,因为这确实有风险僵局。

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

解决这个问题的一种方法是锁定升级,其实际上是零碎回落到全局锁定。实质上,这意味着如果遍历重试循环的次数过多,则会设置全局信号量,使所有线程都进入全局锁定模式一段时间。一段时间可能是一些行动或一段时间,或直到全球锁的争夺平息!

所以最后的答案是“是的,除非它不能在这种情况下不起作用,否则绝对有可能”。

+0

再次仔细阅读#2。问题在于,如果不锁定第一个互斥锁,我无法安全地访问第二个互斥锁。锁定第一个互斥锁会导致根据#1的死锁而不锁定它 - 在竞态条件下,由于“伙伴”指针保持不受保护。 –

+0

@yurikilochek只有'std :: lock'坏了! 'std :: lock'使用了一个死锁避免算法。这是它的目的。确保你一次性锁定你需要的一切!注意:虽然你的析构函数是一个担心。如果你已经触发了析构函数,我认为你已经(通过其他同步手段)确保没有其他线程可以在销毁中访问该对象。但是这并不(必然)适用于它的“伙伴”。 http://en.cppreference.com/w/cpp/thread/lock – Persixty

+0

@yurikilochek我试图在编辑中澄清我的观点。 – Persixty