1

来自 C++ Concurrency in Action 一书的例子

示例(3.2.4)

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == &rhs)
        return;
    std::lock(lhs.m, rhs.m); #1
    std::lock_guard<std::mutex> lock_a(lhs.m, std::adopt_lock); #2
    std::lock_guard<std::mutex> lock_b(rhs.m, std::adopt_lock); #3
    swap(lhs.some_detail, rhs.some_detail);            
}

当我们在#2 和#3 上时,如果在另一个线程上抛出异常怎么办?

另一个例子(3.2.6)看起来更好(虽然更慢更大,因为unique_guard更贵)

friend void swap(X& lhs, X& rhs)
{
    if (&lhs == &rhs)
        return;        
    std::unique_guard<std::mutex> lock_a(lhs.m, std::defer_lock); #1
    std::unique_guard<std::mutex> lock_b(rhs.m, std::defer_lock); #2
    std::lock(lhs.m, rhs.m); #3
    swap(lhs.some_detail, rhs.some_detail);            
}

3.2.4 的例子不是异常安全吗?还是我错过了什么?谢谢。

天真的例子

class some_big_object;
void swap(some_big_object& lhs,some_big_object& rhs);
class X
{
private:
    some_big_object some_detail;
    std::mutex m;
public:
    X(some_big_object const& sd):some_detail(sd){}
    friend void swap(X& lhs, X& rhs)
    {
        if(&lhs==&rhs)
            return;
        std::lock(lhs.m,rhs.m); #A1
        std::lock_guard<std::mutex> lock_a(lhs.m,std::adopt_lock); #A2
        std::lock_guard<std::mutex> lock_b(rhs.m,std::adopt_lock); #A3
        swap(lhs.some_detail,rhs.some_detail);

        } 
};

void threadA()
{
  X A, B;
  //do something
  swap(A, B);
}

void threadB()
{
   //do something
   throw std::runtime_error("error");
}

void testSwap()
{
  std::thread tA(threadA);
  std::thread tB(threadB);

  tA.join();
  tB.join();
}

我的问题是,如果threadA处理#A2时threadB抛出异常怎么办?互斥锁已经锁定,但 lock_guard 可能还没有为互斥锁做好准备。

4

2 回答 2

4

他们对我来说看起来异常安全。

在第一个示例中,相等比较可能会抛出,或者对std::lock#1 的调用可能会抛出,但如果确实如此,则函数退出而不更改任何内容。#2 和 #3 的对象初始化不能抛出,标准是这样说的。调用swap可能会抛出,但如果它抛出,则互斥锁会被unique_lock析构函数解锁。

在第二个例子中,相等比较可以抛出,#1 和 #2 的初始化是noexceptstd::lock调用可以抛出,但如果它抛出,函数退出没有任何影响。对 swap 的调用可能会抛出,但如果它抛出,则互斥锁会被unique_lock析构函数解锁。

您似乎暗示单独线程中的异常与执行交换的线程交互,但事实并非如此。一个线程中的异常不会影响其他线程的执行。

于 2013-08-04T22:07:27.367 回答
-1

我将不得不拿到这本书,看看它实际上是怎么说的,因为它受到高度重视。尽管如此:

  • 无论情况如何,实现线程安全交换都不是一件容易的事。通常,必须在更高级别处理线程安全问题。

  • 您展示的两种解决方案都不起作用;它们都导致死锁。

  • 唯一可行的解​​决方案是为 的所有 实例设置一个互斥体,并在函数顶部 X使用 a 。unique_guardswap

(最后,这里没有涉及异常。但是您考虑一下它们是对的,因为它们会使原本安全的解决方案变得不安全。)

于 2013-08-04T21:01:36.347 回答