C++中的新特性“synchronized”块提供了什么好处?

laawzig2  于 2023-08-09  发布在  其他
关注(0)|答案(3)|浏览(140)

有一个新的实验特性(可能是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();
}

字符串
我觉得这是多余的。上面的a 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; 
}


我在这里发现的唯一优点是省去了使用全局锁的麻烦。使用synchronized块是否有更多优点?什么时候应该更好?

kse8i1jr

kse8i1jr1#

从表面上看,synchronized关键字在功能上与std::mutex * 相似 *,但通过引入新的关键字和相关的语义(例如包含同步区域的块),它可以更容易地优化这些区域的事务内存。
特别是,std::mutex和朋友原则上对编译器或多或少是不透明的,而synchronized具有显式语义。编译器不能确定标准库std::mutex做什么,并且很难将其转换为使用TM。当std::mutex的标准库实现被更改时,C++编译器应该能够正常工作,因此不能对行为做出许多假设。
此外,如果没有synchronized所需的块提供的显式作用域,编译器很难推断出块的范围-在 * 简单 * 的情况下,如单一作用域的lock_guard,这似乎很容易,但有很多复杂的情况,如如果锁逃逸函数,编译器永远不知道它可以在哪里解锁。

m4pnthwp

m4pnthwp2#

锁通常不能很好地组合。请考虑:

//
// 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.m,其次是acc1.m,而th1,通过将资金从acc1转移到acc0,试图首先获得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);
   }
}


......一个人可以获得事务作为一个整体或根本不做的好处,而不会给BankAccount带来互斥体的负担,也不会因为来自用户代码的冲突请求而冒死锁的风险。

gmxoilav

gmxoilav3#

我仍然认为mutai和锁在许多情况下更好,由于它们的灵活性。
例如,您可以将锁设置为R值,这样锁只能在表达式的持续时间内存在,从而大大减少死锁的可能性。
您还可以通过使用“锁定智能指针”来改进缺少成员mutai的类的线程安全性,该指针持有mutex并仅在引用对象被锁定智能指针持有期间锁定。
synchronized关键字在Windows中已经存在很长时间了,它带有CRITICAL_SECTION。我在Windows工作已经有几十年了,所以我不知道这是否仍然是一件事。

相关问题