Re[4]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 10:16
Оценка:
Здравствуйте, barney, Вы писали:

N>>1. Что за компилятор?


B>Apple LLVM version 9.1.0


Я удивляюсь стилю ассемблера, GCC и Clang используют стиль GCC. Но тут явно что-то другое, ближе к традициям MS. OK, ясно.

N>>CF=0 как раз если было 0, то есть спинлок захвачен. Нафига в этом случае возвращаться на loop? Ты делаешь всё наоборот — если захватил, идёшь на круг

N>>Тут лучше ложится jc outer (и вообще, outer и loop совместить)

B>ммм... моя логика такая:

B>если мьютекс еще не захвачен ( == 0) то вызов 'bts [rbx], eax' в керри положит старое значени 0, и атомарно в память 1
B>(но, до этого момента, другой поток УЖЕ мог прочитать 0 в таком же outer цикле, и перейти на inner этап)
B>теперь проц дает гарантию что lock bts атомарно положит там 1 хотя бы в одном из потоков!
B>т.е у кого то carry будет == 0 а у кого == 1
B>вот у того, кого 0 — тот захватил первым

Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.
Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.
The God is real, unless declared integer.
Re[4]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 10:22
Оценка: -1
Здравствуйте, netch80, Вы писали:

N>Только когда нет работы. Когда она есть — им незачем спать.


Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.
Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
В противном случае — компьютер становиться страшно тормознутым и практически не управляемым
Re[5]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 10:24
Оценка:
N>Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.
N>Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.

Вот сделал без ассемблера, с std::atomic_flag по стопам приведенной тут в ссылках статьи

#include <iostream>
#include <thread>
#include <atomic>

class SpinLock
{
public:
    void lock()
    {
        while(lck.test_and_set(std::memory_order_acquire))
        {}
    }
    
    void unlock()
    {
        lck.clear(std::memory_order_release);
    }
    
private:
    std::atomic_flag lck = ATOMIC_FLAG_INIT;
};

//int mutex = 0;
SpinLock lock;

inline void Lock(int * flag) {
    
    int spins_count = 0;
    
//    __asm {
//    outer:
//        mov rbx,flag
//        mov eax,[rbx]
//        cmp eax,0
//        jne outer
//
//        xor ecx,ecx
//        xor eax, eax
//        mov rbx,flag
//    loop:
//        inc ecx
//        lock bts [rbx], eax   // lock + bts atomic test-and-set
//        jnc loop
//        mov spins_count,ecx
//    }
    
    lock.lock();
    
    // locked
    std::cout << "\nINSIDE_THE_LOCK:" << spins_count << "\n";
    
    lock.unlock();

//    __asm {
//        mov rbx,flag
//        xor eax,eax
//        mov [rbx],eax
//    }
}

void spinner(char * id) {
    while (true) {
        Lock(&mutex);
        // not locked
        std::cout << "·" << "SPINNER:" << id << "·";
    }
}

int main() {
    
    char * Q = "Q";
    char * W = "W";
    
    std::thread t1(spinner, Q);
    std::thread t2(spinner, W);
    
    while (true) {
        // not locked
        std::cout << "•";
    }
}



Все равно SPINNER разрывает вывод inside the lock
Re[5]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 10:29
Оценка:
N>Именно. И если CF=0, то надо не идти на цикл, а считать действие lock завершённым. Поэтому jc должно идти на цикл, а противоположность (прямой ход исполнения или jnc) — выходить из цикла.

Точно!! JC ведь прыгает когда CF=1, те другой поток уже ухватил
Я пофиксил и заработало как надо

N>Но дальше второе — чтобы не задрочить кэш, шины и всю систему постоянными попытками записи, если bts или аналог сорвались, снова идут на циклическое чтение, в ожидании, пока не появится 0.


Вот, поэтому и нужен внешний цикл, который читает и ждет освобождения
и это можно делать неатомарно
Re[6]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 10:36
Оценка:
Здравствуйте, barney, Вы писали:

B> void lock()

B> {
B> while(lck.test_and_set(std::memory_order_acquire))

UPD: стормозил, отменено
The God is real, unless declared integer.
Отредактировано 17.05.2018 12:10 netch80 . Предыдущая версия .
Re[7]: внутренняя реализация std::mutex?
От: barney  
Дата: 17.05.18 11:03
Оценка:
N> while(!lck.test_and_set(std::memory_order_acquire))

все таки без !

занятно что CLANG 6.0 вместо lock bts
делает вот что:

.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        mov     al, 1
        xchg    byte ptr [rip + lock], al
        test    al, al
        jne     .LBB0_1


смотрю на https://godbolt.org/

он xchg считает атомарной инструкцией... странно это все...
Re[8]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 12:22
Оценка:
Здравствуйте, barney, Вы писали:

N>> while(!lck.test_and_set(std::memory_order_acquire))


B>все таки без !


Да, это я стормозил.

B>занятно что CLANG 6.0 вместо lock bts

B>делает вот что:

B>
B>.LBB0_1:                                # =>This Inner Loop Header: Depth=1
B>        mov     al, 1
B>        xchg    byte ptr [rip + lock], al

Ну так это test_and_set превращается в такой атомарный обмен с единицей.
Можно попросить Clang показать, как это выглядит на уровне его LL-представления. Мне 4.0 выдал такое:
[code]
  %5 = atomicrmw xchg i8* getelementptr inbounds ({ { i8 } }, { { i8 } }* @lock, i64 0, i32 0, i32 0), i8 1 acquire


B>он xchg считает атомарной инструкцией... странно это все...


Ничего странного, что он так считает, доки читать надо. Из интеловского мануала про XCHG:

If a memory operand is referenced, the processor’s locking protocol is automatically implemented for the duration of the exchange operation, regardless of the presence or absence of the LOCK prefix or of the value of the IOPL. (See the LOCK prefix description in this chapter for more information on the locking protocol.)
This instruction is useful for implementing semaphores or similar data structures for process synchronization. (See “Bus Locking” in Chapter 8 of the Intel® 64 and IA-32 Architectures Software Developer’s Manual, Volume 3A, for more information on bus locking.)


Это Intel знатные извращенцы, они любое изменение в ISA продумывают даже не на один шаг, а на полшага. Сначала надо было чем-то сделать атомарные операции — подумали, что делается только на обмене, цену проигнорировали, добавили такое поведение в XCHG. Потом узнали (хотя все нормальные люди знали давно), что к обмену далеко не всё сводится, стали дорабатывать другие операции (начиная со всяких ADD и BTS), убрали умолчание LOCK. Самое смешное, что при этом для инструкций, которые по определению используются только для атомарных операций — CMPXCHG, XADD — не сделали такого умолчания, и им надо писать явно LOCK (а XCHG — не надо)! Кого бог хочет наказать, лишает разума...
The God is real, unless declared integer.
Re[8]: внутренняя реализация std::mutex?
От: reversecode google
Дата: 17.05.18 12:29
Оценка:
учитесь у яндекса

ну вы прям точно царь! только тот еще "всея руси"
а вы просто "провторяй"
Re[5]: внутренняя реализация std::mutex?
От: netch80 Украина http://netch80.dreamwidth.org/
Дата: 17.05.18 12:32
Оценка: +2
Здравствуйте, AlexGin, Вы писали:

AG>Здравствуйте, netch80, Вы писали:


N>>Только когда нет работы. Когда она есть — им незачем спать.

AG>
AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.

Не знаю, при каком шедулере такое возможно.
В нормальном, пусть даже при N ядрах у нас N рабочих тредов в пуле, задача реакции на пользователя всё равно получит свои кванты, хоть и заметно меньше.
Если же сделать N-1 рабочий тред, то должно хватить места на заметную активность всех других видов.
(Считаю, что других задач на машине не крутится. А вообще для таких вещей надо делать конфигурируемый параметр.)

AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.

AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым

Уточните, под какой ОС такое происходит. Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.

Но в общем случае я поправку принимаю, с нормальными шедулерами она не должна сильно помешать, а с кривыми — поверю, что помогает.
The God is real, unless declared integer.
Re[5]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 13:00
Оценка: +1
Здравствуйте, AlexGin, Вы писали:

AG>Да, но есть риск получить систему полностью загруженной, когда она перестанет отзываться на действия пользователя.

AG>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.
AG>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
AG>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым

Лучше уменьшить приоритет вычислительной задаче через системный менеджер процессов, например, или из кода.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Отредактировано 17.05.2018 13:01 lpd . Предыдущая версия .
Re[4]: внутренняя реализация std::mutex?
От: andrey.desman  
Дата: 17.05.18 13:59
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Всё зависит от задач.

AG>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.

С чего они тяжелые? Или речь о Windows?
Re[3]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 15:25
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?


Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?
Re[4]: внутренняя реализация std::mutex?
От: lpd Черногория  
Дата: 17.05.18 15:29
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?


Для освобождения процессора необходимо перейти в режим ядра. Инструкция pause, про которую ты говорил, нужна для spinlock, но для mutex ее недостаточно.
У сложных вещей обычно есть и хорошие, и плохие аспекты.
Берегите Родину, мать вашу. (ДДТ)
Re[4]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 15:38
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>Всё зависит от задач.

AG>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.
AG>Насчет критических секций — не вижу ничего предосудительного в их применении.
AG>Часто я аккуратно пользуюсь ими. Здесь важно — НЕ вводить в облась, защищенную секцией много кода:
AG>
AG>::EnterCriticalSection(m_pCS);
AG>// Самые элементарные действия с общими ресурсами
AG>::LeaveCriticalSection(m_pCS);
AG>


Ну и зря. Под Windows, внутри Microsoft CRT, std::mutex не имеет ничего общего с WinAPI Mutex. Там, грубо, простой test_and_set, без захода в ядро.

AG>Заполнять ядра CPU работой — это палка о двух концах:

AG>При работе таких приложений, компьютер очень медленно реагирует на действия пользователя.
AG>Правильнее — где-то в циклах рабочих потоков вводить ::Sleep(100...500);
AG> — чтобы отдать управление системе (исключить жуткие тормоза для end-user-а).

Вот ни разу не встречал такого (только если не долбиться с тяжелым запросом в ядро в цикле). Всегда свои программы профилировали чтобы загрузка СPU была 100%. Если меньше, значит где-то холостые ожидания — значит код не оптимален, с точки зрения многопоточности.
Re[5]: внутренняя реализация std::mutex?
От: Videoman Россия https://hts.tv/
Дата: 17.05.18 15:41
Оценка:
Здравствуйте, lpd, Вы писали:

lpd>Для освобождения процессора необходимо перейти в режим ядра. Инструкция pause, про которую ты говорил, нужна для spinlock, но для mutex ее недостаточно.


Возможно я не внимательно смотрел. В любом случае, если spin-lock не успел — деваться некуда, придется уходить в ядро, но это гораздо эффективней чем просто пользоваться Mutex-ом.
Re[6]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 15:49
Оценка:
Здравствуйте, netch80, Вы писали:

N>Не знаю, при каком шедулере такое возможно.

N>В нормальном, пусть даже при N ядрах у нас N рабочих тредов в пуле, задача реакции на пользователя всё равно получит свои кванты, хоть и заметно меньше.
N>Если же сделать N-1 рабочий тред, то должно хватить места на заметную активность всех других видов.
N>(Считаю, что других задач на машине не крутится. А вообще для таких вещей надо делать конфигурируемый параметр.)

AG>>Вполне возможно, что в цикле рабочего потока ::Sleep(100) — не вызовет катастрофических задержек.

AG>>Однако, вызвав диспетчер потоков позволит пользователю как-то общаться с машиной, когда запущена твоя вычислительная задача.
AG>>В противном случае — компьютер становиться страшно тормознутым и практически не управляемым

N>Уточните, под какой ОС такое происходит.


Последний раз видел такую ситуацию — примерно пять лет назад под Windows XP.
С тех пор я стараюсь (в моих разработках) давать возможность переключиться на диспетчер потоков — это Sleep или WaitingFor... системные вызовы.

N>Честно, у меня как-то не наблюдалось. Но у меня уже лет 20 что сервера, что десктопы/лаптопы почти сплошь Linux и FreeBSD.


А разве в мире Linux что-то принцыпиально иначе? Разве не будет такой же ситуации?
Если на каждом из ядер крутится свой поток, и не даёт возможности "вклиниться" диспетчеру потоков, то там должно быть аналогично.
Хотя, допускаю, что при определенных настройках для Linux этот эффект тормозов можно сделать не так заметным.

N>Но в общем случае я поправку принимаю, с нормальными шедулерами она не должна сильно помешать, а с кривыми — поверю, что помогает.


Предположим, что у нас обычный офисный комп с Win10/Win7.
У меня есть приложение, которое (выполняя определенную работу) — дико тормозит все мои пользовательские действия.
Что я могу сделать, как пользователь? Что могу сделать, если приложение сторонее и я НЕ имею его исходных кодов?
Отредактировано 17.05.2018 15:50 AlexGin . Предыдущая версия .
Re[4]: внутренняя реализация std::mutex?
От: andrey.desman  
Дата: 17.05.18 16:07
Оценка:
Здравствуйте, Videoman, Вы писали:

AD>>Серьезно? А как же очередь ожидающих и приоритизация? То есть, какой-то один поток может бесконечно долго висеть и не захватить мьютекс?


V>Я разве утверждал что ее там нет. Там очередь также реализована в user mope в виде списка с добавлением новых потоков в ее конец. Может быть вы все-таки глянете исходники? Так как std::mutex может "шариться" только внутри одного процесса, то я не вижу проблем всю логику реализовать вне ядра. Вы не согласны?


Не согласен, проблемы будут. Вне ядра получится слишком топорно.


Под Windows, например (VS2013), std::mutex реализован без использования вызовов WinAPI, почти так, как вы и описали, но spin блокировка крутится заданное число циклов (сейчас 4000) и уже по истечении этого времени квант потока отдается (он засыпает на pause). Фактически в С++ рантайм вынесены "кишки" стандартной critical section.


Без использования вызовов WinAPI. Нет.
Квант потока отдается (он засыпает на pause). Нет.

почти так, как вы и описали, но в описании был топорный спин-лок.

Нет, конечно, не утверждал, но учитывая вышенаписанное, ты скорее сам исходники смотрел не шибко внимательно.
Re[5]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 16:09
Оценка:
Здравствуйте, Videoman, Вы писали:

V>Здравствуйте, AlexGin, Вы писали:


AG>>Всё зависит от задач.

AG>>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.
AG>>Насчет критических секций — не вижу ничего предосудительного в их применении.
AG>>Часто я аккуратно пользуюсь ими. Здесь важно — НЕ вводить в облась, защищенную секцией много кода:
AG>>
AG>>::EnterCriticalSection(m_pCS);
AG>>// Самые элементарные действия с общими ресурсами
AG>>::LeaveCriticalSection(m_pCS);
AG>>


V>Ну и зря. Под Windows, внутри Microsoft CRT, std::mutex не имеет ничего общего с WinAPI Mutex. Там, грубо, простой test_and_set, без захода в ядро.

Тогда отлично.
Можно им пользоваться достаточно спокойно!

AG>>Заполнять ядра CPU работой — это палка о двух концах:

AG>>При работе таких приложений, компьютер очень медленно реагирует на действия пользователя.
AG>>Правильнее — где-то в циклах рабочих потоков вводить ::Sleep(100...500);
AG>> — чтобы отдать управление системе (исключить жуткие тормоза для end-user-а).

V>Вот ни разу не встречал такого (только если не долбиться с тяжелым запросом в ядро в цикле).

Хорошо, — вот допустим, что у меня такой вариант "ожидания":
#include <atomic>
 
class SpinLock
{
public:
    void lock()
    {
        while(lck.test_and_set(std::memory_order_acquire)) // здесь, возможно, придется подождать!
        {}
    }
 
    void unlock()
    {
        lck.clear(std::memory_order_release);
    }
 
private:
    std::atomic_flag lck = ATOMIC_FLAG_INIT;
};

Взято отсюда:
http://anki3d.org/spinlock

Теперь предположим, что в результате некоторой ситуации все ядра моего CPU ждут на while в методе lock.
Да, случай практически не вероятный, но всё-таки — полностью НЕ исключаемый.
Загрузка всех секций CPU будет под 100% (здесь я даже не спрашиваю насчет полезноти/бесполезности данной вычислительной работы).
Спрошу только одно — сможет ли пользователь хоть как-то взаимодействовать с ОС? Хоть бы для того, чтобы снять эту зависшую задачу?

V>Всегда свои программы профилировали чтобы загрузка СPU была 100%. Если меньше, значит где-то холостые ожидания — значит код не оптимален, с точки зрения многопоточности.

+100500
Да, с точки зрения многопоточности НЕ оптимален.
Но если я вынужден буду только "топить Reset", чтобы хоть как-то вмешаться в работу системы, то такой код также не является корректным
Re[5]: внутренняя реализация std::mutex?
От: AlexGin Беларусь  
Дата: 17.05.18 16:11
Оценка:
Здравствуйте, andrey.desman, Вы писали:

AD>Здравствуйте, AlexGin, Вы писали:


AG>>Всё зависит от задач.

AG>>Я обычно мьютексы не использую, так как это "тяжёлые" системные объекты.

AD>С чего они тяжелые? Или речь о Windows?

+100500
Да, именно про Windows!
Re[6]: внутренняя реализация std::mutex?
От: okman Беларусь https://searchinform.ru/
Дата: 17.05.18 18:09
Оценка:
Здравствуйте, AlexGin, Вы писали:

AG>while(lck.test_and_set(std::memory_order_acquire)) [b]// здесь, возможно, придется подождать!


Спинлок в такой имплементации — это вообще, как по мне, плохая идея.

Во-первых, постоянно теребить глобальную переменную с xchg/cmpxchg и блокировкой шины — не есть гуд
(bus traffic и все такое). Как минимум, тут надо применять стратегию не "test and set", а
"test test and set" + какую-то разгрузку в виде yield/pause/Sleep/etc.

Во-вторых, на однопроцессорной машине крутиться в спин-цикле бессмысленно, так как лок никто в
это время не освободит.

Ну и в-третьих, если между захватом и освобождением лока поток будет вытеснен, получится
очень некрасивая ситуация по отношению к другим потокам, которые ждут его освобождения.
Особенно если лок не гарантирует порядок захвата. Так легко и систему завесить, особенно если
такой спинлок применять где-нибудь в критических местах — драйверы, всякие системные
обработчики и т.д. Встречал на практике.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.