2017-08-03 78 views
15

有一个新的实验功能(可能是C++ 20),它是“同步块”。该块提供了一段代码的全局锁定。以下是cppreference的示例。C++中新功能“synchronized”块的优点是什么?

#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(); 
} 

我觉得这是多余的。有没有在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; 
} 

我发现这里唯一的好处是,我得救了具有全局锁的麻烦。使用同步块有更多优点吗?什么时候应该首选?

+0

不确定实际情况如何,但cppreference使它听起来像第一个版本保证在第一个示例中按顺序打印,而AFAIK第二个版本没有。 – NathanOliver

+0

@NathanOliver为什么不按顺序打印第一个版本?你能解释一下吗?据我所知,整套代码一次只能执行一次,包括打印,这将使一切顺利进行。这也是互斥体的情况。 –

+2

“虽然同步块在全局锁定下执行,但是实施需要检查每个块内的代码,并针对事务安全的代码使用乐观并发(由可用的硬件事务内存备份)事务安全代码“。 – cpplearner

回答

5

在它的面,所述​​关键字是类似std::mutex功能,但是通过引入新的关键字和相关联的语义(例如块包围同步区域),它可以更容易,以优化这些区域用于事务记忆。

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

此外,在没有通过需要​​块提供一个明确的范围,这是很难让编译器原因有关块的程度 - 这似乎容易在简单案件诸如单个作用域lock_guard ,但是有很多复杂的情况,例如,如果锁定功能在编译器从未真正知道它可以解锁的位置处何时转义出来。

1

锁通常并不完美。试想一下:

// 
// 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,通过移动钱acc1acc0,试图首先采取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带有互斥体,并且不会因用户代码的冲突请求而导致死锁。

相关问题