Tình huống tôi sẽ mô tả diễn ra trên iPad 4 (ARMv7), sử dụng libix libix để khóa/mở khóa mutex. Tôi đã nhìn thấy những điều tương tự trên các thiết bị ARMv7 khác, mặc dù (xem dưới đây), vì vậy tôi cho rằng bất kỳ giải pháp sẽ yêu cầu một cái nhìn tổng quát hơn về hành vi của mutexes và hàng rào bộ nhớ cho ARMv7.Chức năng mutex_unlock có phải là hàng rào bộ nhớ không?
Pseudo mã cho kịch bản:
Chủ đề 1 - Sản xuất dữ liệu:
void ProduceFunction() {
MutexLock();
int TempProducerIndex = mSharedProducerIndex; // Take a copy of the int member variable for Producers Index
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
MutexUnlock();
}
Chủ đề 2 - Tiêu thụ dữ liệu:
void ConsumingFunction() {
while (mConsumerIndex != mSharedProducerIndex) {
doWorkOnData (mSharedArray[mConsumerIndex++]);
}
}
Trước đây (khi vấn đề nảy sinh trên iPad 2), tôi tin rằng mSharedProducerIndex = TempProducerIndex
không phải là sự hoàn hảo ormed atomically, và do đó thay đổi để sử dụng một AtomicCompareAndSwap
để gán mSharedProducerIndex
. Điều này đã làm việc cho đến thời điểm này, nhưng nó chỉ ra tôi đã sai và lỗi đã trở lại. Tôi đoán 'sửa' chỉ thay đổi một chút thời gian.
Bây giờ tôi đã đi đến kết luận rằng vấn đề thực tế là một trong số thực hiện lệnh của viết trong khóa mutex, tức là nếu một trong hai trình biên dịch hoặc phần cứng quyết định sắp xếp lại:
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
.. . đến:
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
... và sau đó người tiêu dùng xen kẽ nhà sản xuất, dữ liệu sẽ chưa được viết khi người tiêu dùng cố đọc nó.
Sau khi một số đọc trên hàng rào bộ nhớ, tôi do đó nghĩ rằng tôi muốn thử di chuyển tín hiệu đến người tiêu dùng bên ngoài mutex_unlock
, tin rằng mở khóa sẽ tạo ra một ký ức hàng rào/hàng rào đó sẽ đảm bảo mSharedArray
đã được ghi vào:
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
MutexUnlock();
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
Điều này, tuy nhiên, vẫn không thành công và dẫn tôi đến câu hỏi nếu một mutex_unlock
chắc chắn sẽ hoạt động như hàng rào viết hay không?
Tôi cũng đã đọc an article from HP đề xuất rằng trình biên dịch có thể di chuyển mã vào (nhưng không phải trong số) crit_sec
s. Vì vậy, ngay cả sau khi thay đổi ở trên, việc viết mSharedProducerIndex
có thể trước hàng rào. Có bất kỳ số dặm nào cho lý thuyết này không?
Bằng cách thêm vào một hàng rào rõ ràng vấn đề đi xa:
mSharedArray[TempProducerIndex++] = NewData; // Copy new Data into array at Temp Index
OSMemoryBarrier();
mSharedProducerIndex = TempProducerIndex; // Signal consumer data is ready by assigning new Producer Index to shared variable
Vì vậy, tôi nghĩ rằng tôi hiểu được vấn đề, và rằng một hàng rào là cần thiết, nhưng bất cứ cái nhìn sâu sắc vào các hành vi của các khóa và tại sao nó doesn' dường như thực hiện một rào cản sẽ thực sự hữu ích.
EDIT:
Về việc thiếu một mutex trong chủ đề của người tiêu dùng: Tôi dựa vào các ghi của int mSharedProducerIndex
là một chỉ dẫn duy nhất và do đó hy vọng người tiêu dùng sẽ đọc một trong hai giá trị mới hay cũ . Hoặc là các trạng thái hợp lệ và cung cấp rằng mSharedArray
được viết theo thứ tự (tức làtrước khi viết mSharedProducerIndex
) điều này sẽ ổn, nhưng từ những gì đã được nói cho đến nay, tôi không thể trả lời về điều này.
Bằng cùng một logic, có vẻ như giải pháp rào cản hiện tại cũng bị thiếu sót, vì ghi mSharedProducerIndex
có thể được di chuyển bên trong hàng rào và do đó có thể được sắp xếp lại không chính xác.
Bạn có nên thêm mutex vào người tiêu dùng, chỉ để hoạt động như một rào cản đọc hay có pragma
hoặc hướng dẫn vô hiệu hóa việc thực hiện không theo trật tự trên nhà sản xuất, chẳng hạn như EIEIO
trên PPC?
Bạn chỉ có thể thực hiện OSMemoryBarrier() ngay sau khi * đọc * 'mSharedProducerIndex', như Steve gợi ý. Thử nghiệm trước đó cho thấy OSMemoryBarrier() nhanh hơn OSSpinLockLock + Unlock() nhanh hơn nhiều so với các mutex. –