Обзор примитивов синхронизации — спинлоки и тайны ядра процессора

    Последняя статья про классические примитивы синхронизации.

    (Наверное, потом напишу ещё одну про совсем уже нетипичную задачу, но это потом.)

    Сегодня мы немножко заглянем в процессор. Чуть-чуть.

    По сути, мы будем говорить про единственный примитив, который принципиально отличается от остальных: спинлок. Spinlock.

    В комментариях к предыдущим заметкам возникла дискуссия — насколько справедливо вообще выделять спинлок как примитив, ведь по сути он — просто мьютекс, верно? Он выполняет ту же функцию — запрещает одновременное исполнение фрагмента кода несколькими параллельными нитями.

    На уровне процесса всё так и есть — различия между спинлоком и мьютексом — чисто технические, вопрос реализации и производительности.

    Но меня эта тема интересует не только с позиции программиста юзерленда, но и с позиции разработчика ядра, а так же и разработчика самих примитивов синхронизации. И тут уже различие принципиально.

    Дело в том, что внутри ядра мьютекс реализован с помощью спинлоков, а вот спинлоки реализованы сами по себе, автономно. Они — действительно базовый примитив. Ниже — только сам процессор.

    Есть и ещё одно, семантическое различие. Мьютекс допускает и предполагает снятие нити с процессора, долгую остановку вызывающей нити. Мьютексом можно запереть объект на час или сутки, это приемлемо и нормально. Спинлок принципиально рассчитан только на кратчайшие приостановки, это всегда работа с неатомарным стейтом объекта. Присваивание группы переменных, небольшой цикл — это максимум того, что можно сделать под спинлоком.

    Итак, иерархия реализации такова: mutex/cond/sema сделаны на базе спинлоков, спинлоки — на базе атомарных операций, предоставляемых процессором. Мы в них немного заглянем сегодня.

    Как устроен спинлок?

    Принцип невероятно прост. Спинлок — это просто переменная, которая содержит ноль или единицу (бывают варианты).

    Если ноль — спинлок свободен, и его можно захватить. Если не ноль — спинлок заперт, и нить, которая желает его захватить, будет ждать, крутясь (spin — вращение) в небольшом цикле непрерывной проверки освобождения спинлока. Вот, собственно, реализация:

        while( !  _spin_try_lock( &(sl->lock)  ) )
            while( sl->lock )
                ;
    


    Операция _spin_try_lock — атомарная, реализована на ассемблере соответствующего процессора. Пытаемся запереть спинлок, если не удалось — крутимся в цикле, пока он заперт, потом снова пытаемся.

    Вот и всё, в целом.

    Теперь детали. Операция _spin_try_lock тоже очень проста:

    #define	_spin_try_lock(p)\
    	(!({  register int _r__; \
    	    __asm__ volatile("movl $1, %0; \n\
    			  xchgl %0, %1" \
    			: "=&r" (_r__), "=m" (*(p)) ); \
    	    _r__; }))
    


    Дело в том, что инструкция xchgl процессора Интел атомарно обменивает местами регистр и переменную в памяти. Атомарно с учётом возможной работы других процессоров, то есть в среде SMP.

    Что происходит? Мы меняем местами значение sl->lock и регистр, в котором лежит единица. Если спинлок не был заперт (sl->lock был равен нулю), он станет равен единице и _spin_try_lock вернёт ноль — мы успешно заперли спинлок. Если sl->lock был равен единице, то в итоге sl->lock после обмена опять будет равен единице, но и результатом _spin_try_lock будет считанное предыдущее значение sl->lock — единица, что означает неуспех захвата спинлока.

    Вообще проблема с атомарными операциями на уровне процессора очень велика. Пока в системе один процессор, этой проблемы нет. Но и ситуации такой реально тоже давно уже нет. Даже в дешёвых машинах уже несколько процессоров, или один многоядерный, и/или гипертредный.

    Для того чтобы операция с памятью отработала атомарно, нужно гарантировать, что никакой процессор в системе не будет прикасаться к данной ячейке памяти. Это реализуется в рамках межпроцессорной коммуникации в системе управления когерентностью кеш-памяти в виде запрета на доступ остальным процессорам к ячейке памяти.

    Интересно, что есть совершенно иная схема работы с атомарными объектами. Процессоры mips не имеют инструкции вида «атомарно что-то сделать с ячейкой памяти». Вместо этого у MIPS есть интересная связанная пара инструкций:

    LL - load linked
    SC - store conditional


    Первая инструкция — load linked — просто читает значение из памяти. Но при этом в процессоре взводится триггер, который «следит» за считанной ячейкой — не было ли в неё записи.

    Вторая — store conditional — сохраняет значение в память. Но! Если со времени исполнения load linked в эту ячейку кто-то уже записал новое значение, store conditional вернёт признак неуспеха (и, конечно, ничего в ячейку памяти записывать не будет).

    Как оно устроено внутри
    Судя по всему, механизм MIPS гораздо дешевле в реализации, потому что для этого используются уже существующие механизмы синхронизации состояния кеш-памяти между процессорами. Упомянутый триггер сбрасывается, если процессор получает апдейт состояния кеша для адреса, на котором «висит» триггер. То есть «атомарная» операция не требует от соседних процессоров никакой работы по обслуживанию атомарности.


    В отличие от Интеловского xchg такая пара позволяет делать не только спинлоки, но и реализовывать более сложные алгоритмы. Вернёмся к разговору о семафорах:

        rpos = atomic_add( &read_pos, 1 );
        ret = buf[rpos];
        read_pos %= sizeof(buf);
    


    Все проблемы с этим кодом на процессоре MIPS можно решить довольно просто:

    do 
    {
        rpos = load_linked( &read_pos )
        ret = buf[rpos++];
        rpos %= sizeof(buf);
    } while( !store_conditional( &read_pos, rpos ) );
    


    К сожалению, такой код будет непереносим. Хотя и довольно эффективен. Впрочем, для реально критических участков можно выполнить и процессоро-зависимую оптимизацию.

    Что ещё важно знать про спинлоки в среде ядра ОС (или при программировании для микроконтроллеров, где, как правило, нет режима пользователя): при захвате спинлока прерывания должны быть запрещены.

    Дело в том, что спинлок может защитить нас от другого процессора, но не от нашего же — если внутри запертого спинлока случится прерывание и в прерывании процессор попробует захватить тот же спинлок, наступит клинч: до возврата из прерывания отпереть спинлок некому, а прерывание не закончится, пока спинлок не отперт.

    Поэтому типовая реализация спинлоков или запрещает прерывания сама, или, по крайней мере, проверяет, не забыл ли их запретить вызывающий код.

    Возвращаясь к вопросу о мьютексах и спинлоках: внутри спинлока много чего нельзя. Нельзя спать — нас ждут, нельзя переключать контекст (отдавать процессор) — это чревато дедлоком аналогично ситуации с прерываниями. Нельзя вызывать функции, которые имеют шанс выполнить вышеперечисленное. Например, в ядре или в коде микроконтроллера может быть невозможно вызвать malloc — как правило, он сам синхронизирован и может при нехватке памяти ожидать её освобождения. Ну и, например, могут быть под запретом функции записи в лог — особенно если они пытаются отправить данные на сервер логирования через syslog over UDP.

    При всём сказанном выше в прерываниях спинлоками пользоваться можно и нужно. Собственно, только ими и можно. Правда, это имеет смысл только если в машине несколько процессоров. Иначе вся синхронизация сводится к запрету прерываний, включая запрет прерываний при исполнении собственно прерывания.

    На этом сочтём обзор традиционных примитивов синхронизации завершённым. Через недельку я постараюсь вернуться к теме и окончательно взорвать вам мозг статьей о проблемах реализации примитивов синхронизации в среде персистентной виртуальной памяти. :)

    Это серия статей «Обзор примитивов синхронизации»:
    Share post

    Comments 44

      +1
      Дело в том, что внутри ядра мьютекс реализован с помощью спинлоков, а вот спинлоки реализованы сами по себе, автономно. Они — действительно базовый примитив. Ниже — только сам процессор.

      Никто не мешает реализовать мьютекс что внутри ядра, что вне его при помощи атомарной compare-and-exchange операции, как это сделано с CS в Win. И ниже будет только процессор.
        0
        Ну так эти CAS операции внутри мьютексов являются частью спинлоков. Иначе как?
          0
          Я не понимаю вашего возражения.
          Простой compare-and-exchange — самостоятельная операция, реализованная в CPU и спинлок ею пользуется. Как и мьютекс. Это не делает верным высказывание "мьютекс реализован с помощью спинлоков, а вот спинлоки реализованы сами по себе".
          Мьютекс — это и есть lock. Spinlock — подвид lock'а, со специфичной реализацией. Поэтому, опуская детали реализации, "мьютекс реализован с помощью спинлоков, а вот спинлоки реализованы сами по себе" = "lock реализован при помощи lock'а, а вот lock — автономно, сам по себе".

          Самый обычный EnterCriticalSection/LeaveCriticalSection — это тоже lock (он же mutex), который реализован на compare-and-exchange операции.
            0
            Поставлю вопрос иначе. Как на счет мьютекса без спинлока?
              0
              Никаких проблем, см. выше. На секундочку — не ведется речь об эффективности и т.п. Это чисто теоритический вопрос.
                0
                Вы немогли бы схематично изобразить код такого мьютекса?
                  0
                  Без спинлока.
                    –3
                    Схематично:
                    1) Атомарная проверка и обмен
                    2) Удалось захватить? Выход
                    3) Не удалось — п.1

                    Да, это неэффективно — профессор простаивает, но это другой вопрос. Но реализуется на любом кольце. Только не говорите, пожалуйста, что переход от 3 к 1 это спин, потому что спинлок выделяется особо из-за такого цикла, прокрученного в userspace некоторое кол-во раз перед отдачей своего кванта времени и перехода ко сну в противоположность системе, где при первой неудаче происходит переход ко сну. Здесь же цикл не ограничен ничем, и он может быть на любом уровне, даже если нет разделения на kernel и userspace.
                      +6
                      Это Вы написали спинлок. :)

                      А вот реализация мьютекса. Реальная. https://github.com/dzavalishin/phantomuserland/blob/master/phantom/threads/mutex.c

                      Попробуйте переписать без спинлока. Обратите внимание на thread_block — ему передаётся запертый спинлок, который отпирается "на той стороне" переключения контекста. И не зря. Иначе может случиться ситуация, когда mutex_unlock попытался разбудить нас до того, как нить реально покинула процессор, и будет беда. Поверьте — будет. Я пробовал.
                        –1
                        Хорошо, я допускаю, что неверно использовал термин spinlock, а вы правы по этой части, приношу мои извинения. Но так или иначе, если пользоваться этой терминологией, то спинлок в чистом виде — это тоже реальная реализация мьютекса, пусть и неэффективная.

                        Но это все равно не меняет того, что спинлок — только реализация синхронизационного примитива "мьютекс", а не примитив сам по себе.
                          +2
                          Часто под mutex'ом понимают примитив, имеющим дополнительную семантику, связанную с переключением контекста при невозможности его захвата. Спинлок же сам никогда не паркует тред (если говорить про userspace) и не допускает вытеснения в принципе (если говорить про kernelspace/firmware). Из-за этой семантики классические мьютексы и семафоры не используются, если необходимо избежать переключения контекста.

                          Если не вносить такую семантику в понятие mutex'а, то он становится просто более общим термином в соответствии с оригинальным значением. Так что спор выглядит терминологическим.
                            0
                            Часто под mutex'ом понимают примитив, имеющим дополнительную семантику, связанную с переключением контекста при невозможности его захвата

                            Верно ли это? По мне, если говорить не академическим языком, мьютекс — черный ящик, который может быть в каждый момент времени захвачен только одним потоком. Спинлок ли это в чистом виде, комбинация спинлока и засыпания или попытка захвата и засыпание — детали реализации.

                            Но да, это терминологический вопрос. Чаще всего в литературе встречается (по моему опыту) указание, как синхронизационных примитивов, мьютекса и семафора. Иногда монитора, но это отдельный разговор.
                              0
                              Кажется, описана критическая секция в целом.
                                0
                                Это тоже не примитив.
                                0
                                Верно ли это? По мне, если говорить не академическим языком, мьютекс — черный ящик, который может быть в каждый момент времени захвачен только одним потоком. Спинлок ли это в чистом виде, комбинация спинлока и засыпания или попытка захвата и засыпание — детали реализации.

                                У того же Таненбаума спинлок, семафор и мьютекс (который рассматривается как частный случай семаформа с максимальным значением 1) разделены. И в описании семаформа и мьютекса есть явное указание, что при неудачной попытке захвата поток блокируется.

                                Монитор несколько более сложный концепт, который может быть описан в терминах семафоров/мьютексов и условный переменных. Или самостоятельно, но в терминах блокировки и условного пробуждения потоков.

                                Сразу отвечу miolini, что CS — вещь дуальная к мьютексам и др., она защищается примитивом синхронизации, как необходимое условие корректной работы программы.
                                  0
                                  У того же Таненбаума спинлок, семафор и мьютекс (который рассматривается как частный случай семаформа с максимальным значением 1) разделены

                                  В той редакции, что есть у меня на руках, спинлок (спин-блокировка) указывается в разделе методов, а не примитивов. Наравне с запретом прерываний, например. К примитивам отнесены мьютекс, семафор и монитор. Это как раз то, о чем я и говорю — мьютекс — это абстрактный примитив, спин — способ реализации.
                                    0
                                    ОК, чтобы быть совсем точным укажу, что у меня перед глазами 3 изд. в переводе издательства "Питер", 2013.

                                    Раздел 2.3, где описаны спинлоки, называется "Взаимодействие процессов". Конкретнее спинлоки описаны в подразделе 2.3.3 "Взаимное исключение с активным ожиданием". В следующем подразделе 2.3.4 фигурирует фраза:
                                    Теперь рассмотрим некоторые примитивы взаимодействия процессов, которые блокируют работу, пока им не разрешается входить в критическую область, вместо напрасной траты времени центрального процессора.
                                    Далее описаны примитивы sleep/wakeup. Ещё дальше, в 2.3.5-2.3.6 описаны семафоры и мьютексы. А после — мониторы. При чтении у меня складывается впечатление, что все описанные вещи — примитивы синхронизации, но в разных системах и на разных уровнях примитивами являются те или иные объекты, над которыми уже достраивается остальное.

                                    Почему вы называете мьютекс примитивом, если он прекрасно определяется через семафор?

                                    Как в userspace linux примитивом является futex, в реализации pthreads NPTL (glibc) примитивами обзывают мьютексы, join и прочие радости, хотя они реализованы через futex:
                                    In NPTL, thread synchronization primitives (mutexes, thread joining, and so on) are implemented using the Linux futex(2) system call.

                                    В java примитивами синхронизации будут мониторы (syncronized/wait/notify) и CAS-операции.

                                    Как можно увидеть, есть некоторая естественная путаница, т. к. на разных уровнях абстракции примитивами IPC и синхронизации являются различные конструкции. Общее у них то, что на соответствующем уровне все примитивы для следующего уровня описываются и/или реализованы через примитивы текущего уровня. В общем, примитивы — базис описание операций синхронизации/IPC, но он не обязан быть единственным.
                                      0
                                      Раздел 2.3, где описаны спинлоки, называется «Взаимодействие процессов»… При чтении у меня складывается впечатление, что все описанные вещи — примитивы синхронизации, но в разных системах и на разных уровнях примитивами являются те или иные объекты, над которыми уже достраивается остальное

                                      Это и так и не так. Мы сталкиваемся здесь с трудностями перевода. Дело в том, что в оригинале, в англоязычном издании, данный раздел называется Interprocess Communication. И в предисловии вы можете увидеть рассуждение о том, что communication, собственно, состоит из разных частей/проблем: как передать само сообщение физически от процесса к процессу (т.е. всякие send/receive message, доступ к address space другого процесса и т.п.), как обеспечить синхронизацию и еще один пункт.
                                      Поэтому весь раздел в целом говорит о communication, а синхронизация, которую мы обсуждаем — только один из его аспектов. Как я назвал выше — реализация, деталь реализации.

                                      Почему вы называете мьютекс примитивом, если он прекрасно определяется через семафор?

                                      Да, логически можно определить мьютекс как частный случай семафора. Но изначально они введены в разное время для разных задач (producer/consumer для семафора, с множественными записью/чтением и просто разделяемый ресурс для мьютекса), к тому же мьютекс используется много чаще. Скажем так, это настолько часто используемая и важная вариация семафора, что выделяется как отдельный вид.
                                      Кстати, в свете разговора выше о спинлоке и мьютексе: забавно, что у того же Танненбаума по этому поводу сказано:

                                      They are(мьютексы)
                                      easy and efficient to implement, which makes them especially useful in thread
                                      packages that are implemented entirely in user space.
                                      Because mutexes are so simple, they can easily be implemented in user space
                                      proVided that a TSL or XCHG instruction is available. The code for mutex lock and
                                      mutex_unlock for use with a user-level threads package are shown in Fig. 2-29.
                                      The solution with XCHG is essentially the same.

                                      mutex_lock:
                                      TSL REGISTER,MUTEX
                                      CMP REGISTER,#O
                                      JZE ok
                                      CALL thread_yield
                                      JMP mutex_lock
                                      ok: RET
                                      
                                      mulex_unlock:
                                      MOVE MUTEX,#O
                                      RET
                                      Figure 2·29. Implementation of mutex_lock and mutex_unlock

                                      Вот тебе и "мьютекс или спинлок" и "каноничная реализация мьютекса".
                                        0
                                        Вот тебе и «мьютекс или спинлок» и «каноничная реализация мьютекса».
                                        Это чистая реализация мьютекса, вызов thread_yield кагбэ намекаэ. А то, что она более эффективна, чем спинлок, так это в контексте идеального userspace'а.
                                          0
                                          Какбэ yield просто отдает квант, это спинлок спинлоком.
                                            0
                                            Собственно это обычно и отличает спинлок и мьютекс. Спинлок использует busy wait и не отдаёт свой квант (кроме вытеснения другой задачей/прерыванием), мьютекс же блокирует поток при неудачной попытке захвата. В реализации выше нет spin'а.
                                              0
                                              Это оптимизированный спинлок. :) В user mode спор не имеет смысла. Категорическая (не в плане производительности, а семантическая) разница возникает только в ядре.
                                                0
                                                В user space основная разница, естественно в производительности (в разрезе latency). Как примеры из java-мира — hotspot, где обычный лок использует различные стратегии (biased на базе CAS операций, thin на базе spinlock'а и inflated на базе системного мьютекса). Или jetty, где экономят на переключении контекстов и повторном прогреве кэша после вытеснения dispatch-потока.
                            +1
                            Спинлок это цикл с атомарными попытками захватить ресурс. Userspace здесь не имеет значения.

                            "In software engineering, a spinlock is a lock which causes a thread trying to acquire it to simply wait in a loop ("spin") while repeatedly checking if the lock is available."
                            https://en.wikipedia.org/wiki/Spinlock
                              0
                              И вот (практичемски канонический) pthread mutex:

                              int pthread_mutex_lock(pthread_mutex_t *mutex) {
                                if (mutex->kind == PTHREAD_MUTEX_NORMAL){
                                  if (atomic_exchange(&mutex->lock, 1) != 0) {
                                    while (atomic_exchange(&mutex->lock, -1) != 0) {
                                      if (waitone(mutex->event, INFINITE) != 0) return EINVAL;
                                    }
                                  }
                                } else {
                                  pthread_t self = pthread_self();
                              
                                  if (atomic_exchange(&mutex->lock, 1) == 0) {
                                    mutex->recursion = 1;
                                    mutex->owner = self;
                                  } else {
                                    if (pthread_equal(mutex->owner, self)) {
                                      if (mutex->kind == PTHREAD_MUTEX_RECURSIVE) {
                                        mutex->recursion++;
                                      } else {
                                        return EDEADLK;
                                      }
                                    } else {
                                      while (atomic_exchange(&mutex->lock, -1) != 0) {
                                        if (waitone(mutex->event, INFINITE) != 0) return EINVAL;
                                        mutex->recursion = 1;
                                        mutex->owner = self;
                                      }
                                    }
                                  }
                                }
                              
                                return 0;
                              }
                                0
                                Чистый спинлок — тоже мьютекс, если исключить waitone(mutex->event, INFINITE) и использовать процессорное время полностью.
                                  +2
                                  С утра очень удобно яичницу жарить будет. ;-)
                                    0
                                    А то. Но вопрос был о теоритическом делении.
                                +1
                                Userspace здесь не имеет значения.

                                Да, я ошибся по этой части, мои извинения. Почему-то в голове отложилось, что спинлок это конкретно вид мьютекса со спином именно в userspace, а не любой мьютекс с проверкой в цикле.
                            +2
                            Он будет со спинлоком. Просто код спинлока придётся написать руками. Впрочем, есть вариант — можно всю работу попробовать организовать lockless, но, боюсь, будет очень тяжко. И ещё: по некоторым причинам, переключение контекста несёт через себя запертый спинлок, который обязательно отпирается только после того, как переключение завершено. Это вообще неясно как сделать без спинлока.

                            На юзерлевеле этого всего не видно, а в ядре снег тает, и суть вещей проявляется. :)

                            Если найду время — напишу заметку про контекст свитч и синхронизацию внутри машинерии мьютексов и переключения тредов.
                          +2
                          Спор в изрядной степени терминологический. Атомарные операции процессора сами по себе не являются достаточными и удобными — спинлок поверх них реализован не зря.

                          Но, в принципе, и сам мьютекс можно не делать, как и тред свитч — вписывать каждый раз в код конкретной функуции реализацию mutex_lock и thread switch...

                          Ну, то есть, логика дробления на уровни абстракции ( atomic CPU instruction, spinlock, mutex) необязательна и спорна. Но — сложилась. У спинлока есть некая семантика, отличная от atomic CPU instructions, и она востребована.

                          Но, конечно, я не настаиваю — можно сделать вид, что спинлока нет и писать вместо него его код инлайн. :)
                      0
                      Первая инструкция — load linked — просто читает значение из памяти. Но при этом в процессоре взводится триггер, который «следит» за считанной ячейкой — не было ли в неё записи.

                      Вторая — store conditional — сохраняет значение в память. Но! Если со времени исполнения load linked в эту ячейку кто-то уже записал новое значение, store conditional вернёт признак неуспеха (и, конечно, ничего в ячейку памяти записывать не будет).

                      А можно поподробнее?
                      Допустим, несколько ядер (#0,1,2) обратились к одной ячейке. Одно (#1) успело что-то записать, другое (#2) — нет. После этого к ней обратились ядра #3 и #4. Ядро #0, а потом #3 попытались что-то записать. Будет ли хотя бы одна попытка записи успешной? Когда сеанс общения с ячейкой считается законченным, и после нового LL можно будет перезаписать ячейку?
                        0
                        Ядро #0, а потом #3 попытались что-то записать. Будет ли хотя бы одна попытка записи успешной?

                        попытка ядра #0 точно не будет успешной, потому что уже была успешная запись от #1 после LL с #0, успешность попытки #3 зависит от многих обстоятельств. Например, в каких-то MIPS-ах SC может пофейлиться, например, если LL и SC лежат в памяти слишком далеко, или если на процессоре выполнившем LL были какие-то другие обращения к памяти перед выполнением SC, ну или боле простой случай, если, например, произошло какое-то исключение на этом процессоре.

                        Когда сеанс общения с ячейкой считается законченным, и после нового LL можно будет перезаписать ячейку?

                        да, можно инцировать новую RMW последовательность выполнив LL.
                          0
                          например, если LL и SC лежат в памяти слишком далеко

                          В смысле исполняемые инструкции? Ячейка одна и та же.

                          Рассмотрим два сценария.
                          `

                          0, #1, #2 выполнили LL
                          #1 выполнило SC (удачно)
                          #2 выполнило SC (неудачно)
                          #3, #4 выполнили LL (с той же ячейкой)
                          #0 выполнило SC (???)
                          #3 выполнило SC (???)



                          0, #1, #2 выполнили LL
                          #1 выполнило SC (удачно)
                          #2 выполнило SC (неудачно)
                          #3, #4 выполнили LL (с той же ячейкой)
                          #3 выполнило SC (???)
                          #0 выполнило SC (???)

                          `
                          Сценарии различаются двумя последними строчками.
                          Считается, что инициирована новая последовательность? Может ли #0 выполнить SC? Может ли #3 не выполнить SC?
                            0
                            В смысле исполняемые инструкции?

                            очевидно да.

                            В первом сценарии, #0 SC точно не удачно, а #3 SC зависит (как я уже писал). Во втором сценарии, опять же #0 безусловно неудачно, #3 опять же зависит. Конекретные причины, которые приводят к провалу SC нужно смотреть в документации процессора.

                            Считается, что инициирована новая последовательность?

                            каждый процессор выполнивший LL инициарует RMW последовательность, завершается последовательность выполнением SC, удачным или нет.
                        0
                        А где же ссылка на великого Дейкстру и операции P (Probeer) и V (Verhoog) (голл.) ?
                          0
                          Это же не исторический труд… Всего не упомянешь. Хотя, признаться, я хотел про него написать, но вылетело из головы в процессе.
                          0
                          Это интересно. Разработчики архитектуры MIPS написали мне сегодня, что в современных MIPS-ах есть возможность при реализации спинлока использовать пару LL+PAUSE, чтобы заснуть до тех пор, пока объект, прочитанный через LL не изменится.

                          На минуточку, это позволяет реализовать spinless spinlock! :)
                            0
                            Получается примерно как на arm cortex-a/r с использованием wfe/sev?
                              0
                              Да, на вид — очень похоже.
                            0
                            Спасибо.

                            А у меня такой наивный вопрос: если спинлок — это такая нужная и важная вещь, то почему intel не реализовала его аппаратно? Т.е. процессора через собственную шину (через которую кеши инвалидируют) договариваются о блокировке, как только блокировка снята, всем ожидающим приходит уведомление. Очевидный плюс — спинлок вместо 100% CPU делает что-то типа "sleep'а".

                            Т.е. почему нет операции SPNLK $ADDR или как-то так?
                              +1
                              В mips и arm есть. Может быть, и Интел уже сделал...
                              0
                              Любопытно, что в версиях процессоров TI C64xx команды синхронизации LL/SC были, а в TI C66xx исчезли. Насколько я понял, их роль выполняют DMA, shared memory, ещё что-то… но от атомарных команд решили отказаться.
                                0
                                Более того, они отказались от когерентности кэша.

                                См. Multicore Programming Guide

                                The TCI66xx and C66xx devices do not support automated cache coherency because of the power consumption involved and the latency overhead introduced. Realtime applications targeted for these devices require predictability and determinism, which comes from data coherency being coordinated at select times by the application software. As developers manage this coherency, they develop designs that run faster and at lower power because they control when and if local data must be replicated into different memories.

                              Only users with full accounts can post comments. Log in, please.