Mặc dù một mutex có thể được sử dụng để giải quyết các vấn đề khác, lý do chính mà chúng tồn tại là để cung cấp loại trừ lẫn nhau và do đó giải quyết cái được gọi là điều kiện chủng tộc. Khi hai (hoặc nhiều) chủ đề hoặc quy trình đang cố gắng truy cập cùng một biến đồng thời, chúng ta có tiềm năng cho một điều kiện cuộc đua. Hãy xem xét các mã sau đây
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
i++;
}
Các phần bên trong của chức năng này trông rất đơn giản. Đó chỉ là một tuyên bố. Tuy nhiên, một ngôn ngữ lắp ráp giả điển hình tương đương có thể là:
load i from memory into a register
add 1 to i
store i back into memory
Bởi vì tất cả các hướng dẫn ngôn ngữ lắp ráp tương đương đều được yêu cầu để thực hiện thao tác tăng trên i, chúng tôi nói rằng tăng i là một hoạt động không phải là đạn. Một hoạt động nguyên tử là một hoạt động có thể được hoàn thành trên phần cứng với một người bảo đảm không bị gián đoạn một khi việc thực hiện lệnh đã bắt đầu. Tăng i bao gồm một chuỗi gồm 3 hướng dẫn nguyên tử. Trong một hệ thống đồng thời trong đó một số luồng đang gọi hàm, các vấn đề phát sinh khi một luồng đọc hoặc ghi không đúng lúc. Hãy tưởng tượng chúng ta có hai luồng chạy simultaneoulsy và một luồng gọi hàm này ngay lập tức. Chúng ta cũng nói rằng chúng ta đã khởi tạo thành 0. Ngoài ra, giả sử rằng chúng ta có nhiều thanh ghi và hai luồng đang sử dụng các thanh ghi hoàn toàn khác nhau, do đó sẽ không có xung đột. Thời gian thực tế của những sự kiện này có thể là:
thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1
Điều xảy ra là chúng tôi có hai luồng tăng dần đồng thời, hàm của chúng tôi được gọi hai lần, nhưng kết quả không phù hợp với thực tế đó. Có vẻ như chức năng chỉ được gọi một lần. Điều này là do tính nguyên tử bị "phá vỡ" ở cấp độ máy, có nghĩa là các luồng có thể làm gián đoạn lẫn nhau hoặc làm việc sai thời điểm.
Chúng ta cần một cơ chế để giải quyết điều này. Chúng ta cần áp đặt một số thứ tự cho các hướng dẫn ở trên. Một cơ chế phổ biến là chặn tất cả các luồng trừ một. Mutex Pthread sử dụng cơ chế này.
Bất kỳ luồng nào phải thực thi một số dòng mã có thể sửa đổi một cách không an toàn các giá trị được chia sẻ bởi các luồng khác cùng một lúc (sử dụng điện thoại để nói chuyện với vợ), trước tiên nên thực hiện khóa trên mutex. Theo cách này, bất kỳ luồng nào yêu cầu quyền truy cập vào dữ liệu được chia sẻ đều phải thông qua khóa mutex. Chỉ sau đó, một chủ đề sẽ có thể thực thi mã. Phần mã này được gọi là phần quan trọng.
Khi luồng đã thực hiện phần quan trọng, nó sẽ giải phóng khóa trên mutex để một luồng khác có thể có được khóa trên mutex.
Khái niệm có một mutex có vẻ hơi kỳ quặc khi xem xét con người tìm kiếm quyền truy cập độc quyền vào các vật thể thực, nhưng khi lập trình, chúng ta phải có chủ ý. Các chủ đề và quy trình đồng thời không có sự giáo dục về văn hóa và xã hội mà chúng ta làm, vì vậy chúng ta phải buộc họ chia sẻ dữ liệu độc đáo.
Về mặt kỹ thuật, làm thế nào để một mutex hoạt động? Nó không phải chịu các điều kiện chủng tộc giống như chúng ta đã đề cập trước đó? Không phải pthread_mutex_lock () phức tạp hơn một chút mà là một bước tăng đơn giản của một biến?
Về mặt kỹ thuật, chúng tôi cần một số hỗ trợ phần cứng để giúp chúng tôi. Các nhà thiết kế phần cứng cung cấp cho chúng tôi các hướng dẫn máy làm nhiều hơn một việc nhưng được đảm bảo là nguyên tử. Một ví dụ kinh điển của một hướng dẫn như vậy là test-and-set (TAS). Khi thử lấy khóa trên tài nguyên, chúng tôi có thể sử dụng TAS có thể kiểm tra xem giá trị trong bộ nhớ có bằng 0. Nếu đó là tín hiệu của chúng tôi rằng tài nguyên đó đang được sử dụng và chúng tôi không làm gì cả (hoặc chính xác hơn , chúng tôi chờ đợi bằng một số cơ chế. Một mutth pthreads sẽ đưa chúng tôi vào một hàng đợi đặc biệt trong hệ điều hành và sẽ thông báo cho chúng tôi khi tài nguyên có sẵn. Các hệ thống Dumber có thể yêu cầu chúng tôi thực hiện một vòng lặp chặt chẽ, kiểm tra tình trạng lặp đi lặp lại) . Nếu giá trị trong bộ nhớ không phải là 0, TAS sẽ đặt vị trí thành một giá trị khác 0 mà không sử dụng bất kỳ hướng dẫn nào khác. Nó ' Giống như kết hợp hai hướng dẫn lắp ráp thành 1 để cung cấp cho chúng ta tính nguyên tử. Do đó, kiểm tra và thay đổi giá trị (nếu thay đổi là phù hợp) không thể bị gián đoạn một khi nó đã bắt đầu. Chúng ta có thể xây dựng các mutexes trên đầu một hướng dẫn như vậy.
Lưu ý: một số phần có thể xuất hiện tương tự như câu trả lời trước đó. Tôi đã chấp nhận lời mời của anh ấy để chỉnh sửa, anh ấy thích cách ban đầu của nó, vì vậy tôi đang giữ những gì tôi có được truyền vào một chút lời nói của anh ấy.