2017-08-03 4 views
15

新しい実験的な機能(おそらくC++ 20)があります。これは「同期ブロック」です。このブロックは、コードセクションにグローバルロックを提供します。次は、cppreferenceの例です。C++の新機能「同期」ブロックにはどんな利点がありますか?

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

私はそれが余分だと感じます。上記からの同期ブロックの間に違いはありますが、この1:

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

実際にそうであるかどうかはわかりませんが、最初のバージョンがAFAIKの2番目のバージョンではなく、最初のバージョンでは保証されているように見えます。 – NathanOliver

+0

@ NathanOliverなぜ最初のバージョンが順番に印刷されないのですか?あなたは説明できますか?私が理解している限り、一連のコードは一度に一度だけ実行されます。印刷はすべてを順番に実行します。ミューテックスの場合もそうです。 –

+2

"同期化されたブロックはグローバルロックのもとでas-ifを実行しますが、実装は各ブロック内のコードを検査し、トランザクションセーフなコードに対してはオプティミスティックな同時実行性(ハードウェアのトランザクションメモリによってバックアップされます)トランザクションセーフコード " – cpplearner

答えて

5

、​​キーワードは、機能std::mutexから類似であるが、新しいキーワードおよび関連するセマンティクスを(そのようなブロックが同期領域を囲む)を導入することによって、それははるかに容易にトランザクションのためのこれらの領域を最適化することができますメモリ。

特に、std::mutexと友人は、原則としてコンパイラに対して多かれ少なかれ不透明ですが、​​は明示的なセマンティクスを持っています。コンパイラは、標準ライブラリstd::mutexが何をしているのかわからず、TMを使用するように変換するのは苦労します。 C++コンパイラは、std::mutexの標準ライブラリ実装が変更されたときに正しく動作することが期待されるため、動作について多くの仮定をすることはできません。コンパイラは、ブロックの程度について推論するため​​ために必要とされるブロックによって提供される明示的な範囲せず、それは困難である。また

、 - それは、そのような単一スコープ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は、acc1acc0からお金を移動することで、 は最初acc0.macc1.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にmutexを負わせず、ユーザーコードからの要求が競合してデッドロックが発生することもありません。

関連する問題