2016-02-07 16 views
8

Tôi đã đạt đến một điểm trong dự án yêu cầu liên lạc giữa các luồng trên tài nguyên rất tốt có thể được ghi vào, vì vậy việc đồng bộ hóa là điều bắt buộc. Tuy nhiên tôi không thực sự hiểu đồng bộ hóa ở bất cứ điều gì khác hơn mức cơ bản.std :: ví dụ lock_guard, giải thích lý do tại sao nó hoạt động

xem xét ví dụ cuối cùng trong liên kết này: http://www.bogotobogo.com/cplusplus/C11/7_C11_Thread_Sharing_Memory.php

#include <iostream> 
#include <thread> 
#include <list> 
#include <algorithm> 
#include <mutex> 

using namespace std; 

// a global variable 
std::list<int>myList; 

// a global instance of std::mutex to protect global variable 
std::mutex myMutex; 

void addToList(int max, int interval) 
{ 
    // the access to this function is mutually exclusive 
    std::lock_guard<std::mutex> guard(myMutex); 
    for (int i = 0; i < max; i++) { 
     if((i % interval) == 0) myList.push_back(i); 
    } 
} 

void printList() 
{ 
    // the access to this function is mutually exclusive 
    std::lock_guard<std::mutex> guard(myMutex); 
    for (auto itr = myList.begin(), end_itr = myList.end(); itr != end_itr; ++itr) { 
     cout << *itr << ","; 
    } 
} 

int main() 
{ 
    int max = 100; 

    std::thread t1(addToList, max, 1); 
    std::thread t2(addToList, max, 10); 
    std::thread t3(printList); 

    t1.join(); 
    t2.join(); 
    t3.join(); 

    return 0; 
} 

Ví dụ trình bày cách ba chủ đề, hai nhà văn và một người đọc, truy cập một tài nguyên chung (danh sách).

Hai chức năng toàn cầu được sử dụng: một được sử dụng bởi hai luồng của người viết và một được sử dụng bởi chuỗi trình đọc. Cả hai chức năng đều sử dụng một lock_guard để khóa cùng một tài nguyên, danh sách.

Bây giờ ở đây là những gì tôi chỉ không thể quấn đầu của tôi xung quanh: Người đọc sử dụng một khóa trong một phạm vi khác nhau hơn hai chủ đề nhà văn, nhưng vẫn khóa xuống cùng một nguồn tài nguyên. Làm thế nào có thể làm việc này? Sự hiểu biết hạn chế của tôi về mutexes cho chính nó tốt đến chức năng của người viết, ở đó bạn có hai luồng bằng cách sử dụng cùng một hàm chính xác. Tôi có thể hiểu rằng, một kiểm tra được thực hiện ngay khi bạn chuẩn bị bước vào khu vực được bảo vệ, và nếu ai đó đã ở trong đó, bạn chờ đợi.

Nhưng khi phạm vi khác biệt? Điều này sẽ chỉ ra rằng có một số loại cơ chế mạnh hơn chính quy trình đó, một số loại môi trường thời gian chạy ngăn chặn việc thực thi luồng "trễ". Nhưng tôi nghĩ rằng không có những điều như vậy trong c + +. Vì vậy, tôi đang thua lỗ.

Chính xác những gì diễn ra dưới mui xe ở đây?

+1

Tôi muốn thêm rằng khóa mutex là [nguyên tử] (http://stackoverflow.com/questions/15054086/what-does-atomic-mean-in-programming). Hi vọng điêu nay co ich. – Incomputable

Trả lời

3

myMutex là toàn cầu, được sử dụng để bảo vệ myList. guard(myMutex) chỉ đơn giản là tham gia khóa và lối ra từ khối gây ra sự phá hủy của nó, dis-hấp dẫn khóa. guard chỉ là một cách thuận tiện để thu hút và không tham gia khóa.

Bằng cách đó, mutex không bảo vệ bất kỳ dữ liệu nào. Nó chỉ cung cấp một cách để bảo vệ dữ liệu. Đây là mẫu thiết kế bảo vệ dữ liệu. Vì vậy, nếu tôi viết chức năng của riêng tôi để sửa đổi danh sách như dưới đây, mutex không thể bảo vệ nó.

void addToListUnsafe(int max, int interval) 
{ 
    for (int i = 0; i < max; i++) { 
     if((i % interval) == 0) myList.push_back(i); 
    } 
} 

Khóa chỉ hoạt động nếu tất cả các đoạn mã cần truy cập dữ liệu tham gia khóa trước khi truy cập và tắt sau khi hoàn tất. Đây thiết kế mẫu của việc tham gia và dis-tham gia các khóa trước và sau mỗi lần truy cập là những gì bảo vệ dữ liệu (myList trong trường hợp của bạn)

Bây giờ bạn sẽ tự hỏi, tại sao sử dụng mutex chút nào, và tại sao không, nói rằng, một bool. Và có bạn có thể, nhưng bạn sẽ phải đảm bảo rằng biến số bool sẽ thể hiện một số đặc điểm bao gồm nhưng không giới hạn trong danh sách dưới đây.

  1. Không được lưu trữ (dễ bay hơi) trên nhiều luồng.
  2. Đọc và viết sẽ là hoạt động nguyên tử.
  3. Khóa của bạn có thể xử lý tình huống có nhiều đường ống thực thi (lõi logic, v.v.).

Có các cơ chế khác nhau: "khóa tốt hơn" đó chỉ là đủ cho tình huống của bạn.

2

Đây chính là cách khóa. Khi một chủ đề lấy khóa, bất kể vị trí trong mã nó làm như vậy, nó phải đợi đến lượt nó nếu một sợi khác giữ khóa. Khi một luồng giải phóng một khóa, bất kể vị trí trong mã nó làm như vậy, một luồng khác có thể lấy khóa đó.

Khóa bảo vệ dữ liệu chứ không phải mã. Họ làm điều đó bằng cách đảm bảo tất cả các mã truy cập dữ liệu được bảo vệ thực hiện trong khi nó giữ khóa, không bao gồm các chủ đề khác từ bất kỳ mã nào có thể truy cập cùng dữ liệu đó.

8

Hãy có một cái nhìn tại dòng tương ứng:

std::lock_guard<std::mutex> guard(myMutex); 

Chú ý rằng các tài liệu tham khảo lock_guard các toàn cầu mutex myMutex. Đó là, cùng một mutex cho cả ba chủ đề. Có gì lock_guard làm về cơ bản đây là:

  • Khi xây dựng, nó khóa myMutex và giữ một tham chiếu đến nó.
  • Sau khi hủy (nghĩa là khi phạm vi của người bảo vệ bị bỏ lại), nó sẽ mở khóa myMutex.

Mutex luôn giống nhau, nó không liên quan gì đến phạm vi. Điểm lock_guard chỉ là để khóa và mở khóa mutex dễ dàng hơn cho bạn. Ví dụ: nếu bạn theo cách thủ công lock/unlock, nhưng chức năng của bạn ném một ngoại lệ ở đâu đó ở giữa, nó sẽ không bao giờ đạt đến tuyên bố unlock. Vì vậy, thực hiện theo cách thủ công bạn phải đảm bảo rằng mutex là luôn mở khóa.Mặt khác, đối tượng lock_guard bị hủy tự động bất cứ khi nào chức năng bị thoát - bất kể nó bị thoát ra sao.

+1

Bạn có quyền ofcourse, nhưng tôi sợ tôi đã được missunderstood. Điều tôi thực sự muốn nói là cách hoạt động ở mức thấp. Một phần dữ liệu chỉ là bộ nhớ được đặt thành thứ gì đó. Nếu bạn muốn bảo vệ dữ liệu đó, bạn phải đóng gói nó. Nó không xuất hiện như bất cứ thứ gì như vậy ở nơi làm việc ở đây, bạn có thể dễ dàng khóa một int đơn giản theo cách tương tự. Vậy cơ chế bảo vệ khóa thể hiện thực sự như thế nào? Vì phạm vi không liên quan, một cái gì đó khác phải giao nhau với chủ đề trễ, tắt nó đi, và đánh thức nó một lần nữa khi tài nguyên được tự do trở lại. Đưa tôi đi? – Deviatore

+0

@Daviatore: Nó được gọi là lịch biểu hệ điều hành – MikeMB

+0

Điều này không thực sự là về đóng gói. Mutex cơ bản là một tài nguyên mà bạn có được khi 'lock()' được gọi. Nếu tài nguyên không thể có được (bởi vì một luồng khác đang nắm giữ nó), điều gì xảy ra phụ thuộc vào việc thực hiện cụ thể; hoặc [thuật toán loại trừ lẫn nhau] (https://en.wikipedia.org/wiki/Mutual_exclusion#Enforcing_mutual_exclusion) được sử dụng hoặc hệ điều hành xử lý nó, đặt chuỗi của bạn lên hàng chờ (xem ví dụ này [hướng dẫn về Chủ đề POSIX Lập trình] (https://computing.llnl.gov/tutorials/pthreads/#Mutexes) về cách hệ thống UNIX/Linux thực hiện). – mindriot

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