2017-08-03 11 views
15

Có một tính năng thử nghiệm mới (có thể là C++ 20), là "khối được đồng bộ hóa". Khối cung cấp khóa toàn cầu trên một phần mã. Sau đây là ví dụ từ cppreference.Tính năng mới, khối "đồng bộ" nào trong C++ cung cấp?

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

Tôi cảm thấy nó không cần thiết. Có sự khác biệt giữa một khối đồng bộ từ trên cao, và điều này:

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; 
} 

Ưu điểm duy nhất tôi tìm thấy ở đây là tôi đang lưu những rắc rối của việc có một khóa toàn cầu. Có nhiều ưu điểm hơn khi sử dụng khối được đồng bộ hóa không? Khi nào nó nên được ưa thích?

+0

Không chắc chắn nếu điều này thực sự là trường hợp nhưng cppreference làm cho nó âm thanh như phiên bản đầu tiên được đảm bảo để in theo thứ tự trong ví dụ đầu tiên trong khi AFAIK phiên bản thứ hai thì không. – NathanOliver

+0

@NathanOliver Tại sao phiên bản đầu tiên không in theo thứ tự? Bạn có thể giải thích? Theo như tôi đã hiểu, toàn bộ mã sẽ chỉ được thực hiện một lần tại một thời điểm, bao gồm cả in ấn, điều này sẽ làm mọi thứ theo thứ tự. Đây cũng là trường hợp của mutex. –

+2

"Mặc dù các khối đồng bộ thực thi như-if dưới khóa toàn cục, các triển khai được dự kiến ​​sẽ kiểm tra mã trong mỗi khối và sử dụng đồng thời lạc quan (được hỗ trợ bởi bộ nhớ giao dịch phần cứng nếu có) cho mã an toàn giao dịch và khóa tối thiểu cho không mã an toàn giao dịch. " – cpplearner

Trả lời

5

Trên mặt của nó, từ khóa synchronizedtương tự-std::mutex chức năng, nhưng bằng cách giới thiệu một từ khóa mới và ngữ nghĩa liên quan (khối như vậy bao quanh khu vực đồng bộ) nó làm cho nó dễ dàng hơn để tối ưu hóa các khu vực này cho giao dịch ký ức.

Cụ thể, std::mutex và bạn bè về nguyên tắc ít nhiều mờ đục đối với trình biên dịch, trong khi synchronized có ngữ nghĩa rõ ràng. Trình biên dịch không thể chắc chắn thư viện tiêu chuẩn std::mutex làm gì và sẽ gặp khó khăn khi chuyển đổi nó sang sử dụng TM. Một trình biên dịch C++ sẽ được dự kiến ​​sẽ hoạt động chính xác khi việc thực hiện thư viện chuẩn của std::mutex bị thay đổi và do đó không thể đưa ra nhiều giả định về hành vi.

Bên cạnh đó, mà không có một phạm vi rõ ràng được cung cấp bởi các khối đó là cần thiết cho synchronized, rất khó cho các trình biên dịch để lý do về mức độ của khối - có vẻ như dễ dàng trong trường hợp đơn giản như một lần scoped lock_guard , nhưng có rất nhiều trường hợp phức tạp, chẳng hạn như nếu khóa thoát khỏi chức năng tại thời điểm đó trình biên dịch không bao giờ thực sự biết nơi nó có thể được mở khóa.

1

Khóa không sáng tác nói chung. Xem xét:

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

Trong trường hợp này, thực tế là th0, bằng cách di chuyển tiền từ acc0 để acc1, được cố gắng để có acc0.m đầu tiên, acc1.m thứ hai, trong khi th1, bằng cách di chuyển tiền từ acc1 để acc0, đang cố gắng để có acc1.m trước tiên, acc0.m giây có thể làm cho chúng bế tắc.

Ví dụ này là quá đơn giản hóa, và có thể được giải quyết bằng cách sử dụng std::lock() hoặc C++ 17 variadic lock_guard -equivalent, nhưng nghĩ đến những trường hợp chung nơi người ta đang sử dụng phần mềm của bên thứ ba, không biết nơi ổ khóa đang được lấy hoặc giải phóng. Trong các tình huống thực tế, việc đồng bộ hóa thông qua các khóa được khéo léo thực sự nhanh chóng.

Các tính năng bộ nhớ giao dịch nhằm mục đích cung cấp đồng bộ hóa bao gồm tốt hơn so với khóa; nó là một tính năng tối ưu hóa của các loại, tùy thuộc vào ngữ cảnh, nhưng nó cũng là một tính năng an toàn. Viết lại move_money_from() như sau:

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

...người ta nhận được lợi ích của giao dịch đang được thực hiện tổng thể hoặc không ở mức tất cả, mà không phải gánh chịu BankAccount với một mutex và không có rủi ro gây deadlocks do xung đột các yêu cầu từ mã người dùng.

Các vấn đề liên quan