新功能有什么优势,"同步"块,在C ++中提供?

时间:2017-08-03 14:28:25

标签: c++ multithreading transactional-memory

有一个新的实验功能(可能是C ++ 20),它是" synchronized块"。该块提供了一段代码的全局锁定。以下是cppreference的示例。

#include <iostream>
#include <vector>
#include <thread>
int f()
{
    static int i = 0;
    synchronized {
        std::cout << i << " -> ";
        ++i;       
        std::cout << i << '\n';
        return i; 
    }
}
int main()
{
    std::vector<std::thread> v(10);
    for(auto& t: v)
        t = std::thread([]{ for(int n = 0; n < 10; ++n) f(); });
    for(auto& t: v)
        t.join();
}

我觉得这是多余的。上面的同步块与此之间有什么区别:

std::mutex m;
int f()
{
    static int i = 0;
    std::lock_guard<std::mutex> lg(m);
    std::cout << i << " -> ";
    ++i;       
    std::cout << i << '\n';
    return i; 
}

我在这里找到的唯一优势是我省去了全局锁定的麻烦。使用同步块有更多优点吗?什么时候应该优先考虑?

2 个答案:

答案 0 :(得分:6)

从表面上看,synchronized关键字在功能上是类似std::mutex,但是通过引入一个新的关键字和相关的语义(例如包含同步区域的块) )它使得为事务存储器优化这些区域变得更加容易。

特别是,std::mutex和朋友原则上或多或少对编译器不透明,而synchronized具有显式语义。编译器无法确定标准库std::mutex的作用,并且很难将其转换为使用TM。当std::mutex的标准库实现发生变化时,C ++编译器可能会正常工作,因此无法对该行为做出许多假设。

此外,如果没有synchronized所需的块提供的显式范围,编译器很难推断块的范围 - 在简单例如单个作用域lock_guard的情况,但是有很多复杂的情况,例如如果锁转义函数,那么编译器永远不知道它可以解锁的位置。

答案 1 :(得分:2)

一般情况下,锁定不会很好。考虑:

//
// includes and using, omitted to simplify the example
//
void move_money_from(Cash amount, BankAccount &a, BankAccount &b) {
   //
   // suppose a mutex m within BankAccount, exposed as public
   // for the sake of simplicity
   //
   lock_guard<mutex> lckA { a.m };
   lock_guard<mutex> lckB { b.m };
   // oversimplified transaction, obviously
   if (a.withdraw(amount))
      b.deposit(amount);
}

int main() {
   BankAccount acc0{/* ... */};
   BankAccount acc1{/* ... */};
   thread th0 { [&] {
      // ...
      move_money_from(Cash{ 10'000 }, acc0, acc1);
      // ...
   } };
   thread th1 { [&] {
      // ...
      move_money_from(Cash{ 5'000 }, acc1, acc0);
      // ...
   } };
   // ...
   th0.join();
   th1.join();
}

在这种情况下,th0通过将资金从acc0转移到acc1,这一事实是 首先尝试acc0.macc1.m秒,而th1,将资金从acc1转移到acc0,首先尝试acc1.macc0.m秒可能使他们陷入僵局。

此示例过于简单,可以使用std::lock()解决 或C ++ 17可变参数lock_guard - 相当于,但请考虑一般情况 在哪里使用第三方软件,不知道锁在哪里 采取或释放。在现实生活中,通过锁进行同步 真的很快。

事务性内存功能旨在提供组合的同步 比锁更好;它取决于具体情况,是各种优化功能,但它也是一种安全功能。重写move_money_from()如下:

void move_money_from(Cash amount, BankAccount &a, BankAccount &b) {
   synchronized {
      // oversimplified transaction, obviously
      if (a.withdraw(amount))
         b.deposit(amount);
   }
}

......人们可以从整体上获得交易的好处,或者不是 所有这些都不会使BankAccount加载互斥锁,并且不会因用户代码的冲突请求而导致死锁。