Мені хотілося знати те саме, тому я виміряв це. У моїй коробці (AMD FX (tm) -8150 Восьмиядерний процесор на частоті 3.612361 ГГц), блокуючи та розблоковуючи розблокований мютекс, який знаходиться у власній лінійці кешу і вже кешований, займає 47 годин (13 нс).
Через синхронізацію між двома ядрами (я використовував процесор № 0 і №1), я міг викликати пару блокування / розблокування лише раз на кожні 102 нс на двох потоках, тож раз на кожні 51 нс, з чого можна зробити висновок, що це займає приблизно 38 ns для відновлення після того, як нитка робить розблокування, перш ніж наступний потік зможе її знову заблокувати.
Програму, яку я використовував для цього, можна знайти тут:
https://github.com/CarloWood/ai-statefultask-testsuite/blob/b69b112e2e91d35b56a39f41809d3e3de2f9e4b8/src/mutex_test.cxx
Зауважте, що у нього є кілька твердо кодованих значень, специфічних для мого поля (xrange, yrange та rdtsc overhead), тому вам, ймовірно, доведеться експериментувати з ним, перш ніж воно буде працювати для вас.
Графік, який він створює в такому стані, є:
Це показує результат тестування еталону на наступному коді:
uint64_t do_Ndec(int thread, int loop_count)
{
uint64_t start;
uint64_t end;
int __d0;
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (start) : : "%rdx");
mutex.lock();
mutex.unlock();
asm volatile ("rdtsc\n\tshl $32, %%rdx\n\tor %%rdx, %0" : "=a" (end) : : "%rdx");
asm volatile ("\n1:\n\tdecl %%ecx\n\tjnz 1b" : "=c" (__d0) : "c" (loop_count - thread) : "cc");
return end - start;
}
Два дзвінки rdtsc вимірюють кількість годин, необхідних для блокування та розблокування `mutex '(з накладними витратами на 39 годин для дзвінків rdtsc у моїй коробці). Третій асм - це цикл затримки. Розмір петлі затримки на 1 нитку менший, ніж для потоку 0, тому нитка 1 трохи швидша.
Вищенаведена функція викликається в тісному циклі розміром 100000. Незважаючи на те, що функція трохи швидша для потоку 1, обидві петлі синхронізуються через виклик до мютексу. Це видно на графіку з того, що кількість годин, виміряних для пари замка / розблокування, трохи більше для потоку 1, щоб врахувати меншу затримку циклу під ним.
У наведеному вище графіку нижня права точка - це вимірювання із затримкою петлі_счет 150, а потім слідуючи за точками внизу, вліво, кількість циклів_наза зменшується на кожне вимірювання. Коли йому стає 77, функція викликається кожні 102 нс в обох потоках. Якщо згодом loop_count ще більше зменшиться, синхронізувати нитки вже неможливо, і мютекс починає фактично блокуватися більшу частину часу, що призводить до збільшення кількості годин, які потрібно зробити для блокування / розблокування. Також через це збільшується середній час виклику функції; тому точки сюжету тепер піднімаються вгору і знову вправо.
З цього можна зробити висновок, що блокування та розблокування мютексу кожні 50 нс не є проблемою для моєї скриньки.
Я в цілому мою висновок, що відповідь на питання про ОП полягає в тому, що додавати більше мутексів краще, якщо це призводить до меншої суперечки.
Спробуйте зафіксувати файли якнайшвидше. Єдиною причиною ставити їх -say- за межами циклу було б, якщо ця петля циклічне швидше, ніж один раз кожні 100 нс (вірніше, кількість потоків, які хочуть виконати цю петлю одночасно, раз 50 нс) або коли 13 нс разів розмір циклу більше затримки, ніж затримка, яку ви отримуєте за суперечкою.
EDIT: Зараз я набагато більше знаю з цього питання і починаю сумніватися у висновку, який я представив тут. Перш за все, CPU 0 і 1 виявляються гіперпотоковими; незважаючи на те, що AMD стверджує, що має 8 справжніх ядер, безумовно, є щось дуже рибне, тому що затримки між двома іншими ядрами значно більші (тобто 0 і 1 утворюють пару, як і 2 і 3, 4 і 5, а також 6 і 7 ). По-друге, std :: mutex реалізований таким чином, що він обертається блокуванням трохи раніше, ніж насправді виконує системні дзвінки, коли не вдається негайно отримати замок на mutex (що, без сумніву, буде надзвичайно повільним). Тож, що я тут виміряв - це абсолютно ідеальна ситуація, і на практиці блокування та розблокування може зайняти значно більше часу за замок / розблокування.
Підсумок, мютекс реалізований з атомами. Для синхронізації атомів між ядрами повинна бути заблокована внутрішня шина, яка заморожує відповідну лінію кешу на кілька сотень тактових циклів. У випадку, якщо блокування неможливо отримати, необхідно виконати системний виклик, щоб покласти нитку у режим сну; що, очевидно, надзвичайно повільно (системні дзвінки мають порядку 10 миркосекунд). Зазвичай це насправді не проблема, тому що ця нитка повинна спати в будь-якому випадку - але це може бути проблемою з великою суперечливістю, коли нитка не може отримати замок протягом часу, який вона зазвичай крутиться, і тому система дзвонить, але CAN візьміть замок незабаром після. Наприклад, якщо кілька потоків блокують і розблоковують мютекс у тісному циклі, і кожен тримає замок на 1 мікросекунд або близько того, тоді вони можуть бути дуже сповільнені тим, що їх постійно засинають і прокидаються знову. Крім того, як тільки нитка спить, і інша нитка повинна її розбудити, ця нитка повинна зробити системний виклик і затримується ~ 10 мікросекунд; ця затримка, таким чином, відбувається під час розблокування мютексу, коли інший потік чекає на цей мютекс у ядрі (після того, як спінінг зайняв занадто довго)