C++中的新功能"synchronized"块有什么优势?

The*_*ist 17 c++ multithreading transactional-memory

有一个新的实验功能(可能是C++ 20),它是"同步块".该块提供了一段代码的全局锁定.以下是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();
}
Run Code Online (Sandbox Code Playgroud)

我觉得这是多余的.上面的一个synchronized块和这个之间有什么区别:

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; 
}
Run Code Online (Sandbox Code Playgroud)

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

Bee*_*ope 6

在它的面,所述synchronized关键字是相似std::mutex功能性,但通过引入新的关键字和相关联的语义(例如块包围同步区域),它可以更容易,以优化这些区域对于事务存储器.

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

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


小智 5

一般来说,锁的组合不太好。考虑:

//
// 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();
}
Run Code Online (Sandbox Code Playgroud)

在这种情况下,th0通过将资金从 转移acc0acc1,试图获取acc0.m第一、acc1.m第二,而th1通过将资金从 转移acc1acc0,试图获取acc1.m第一、acc0.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);
   }
}
Run Code Online (Sandbox Code Playgroud)

...人们可以获得事务作为一个整体完成或根本不完成的好处,而不会增加BankAccount互斥锁的负担,也不会因为用户代码的请求冲突而面临死锁的风险。