Что произойдет, если два процесса на разных процессорах попытаются получить блокировку ТОЧНО в одно и то же время?

Итак, я читаю о синхронизации и различных алгоритмах, таких как спин-блокировки, семафоры и мьютексы, чтобы избежать состояния гонки.

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

Например, предположим, что поток 1 в процессоре A запускает lock(mutex1); снять(1000); разблокировать (мьютекс1);

и поток 2 в процессоре B запускает lock(mutex1); депозит(1000); депозит(1000); разблокировать (мьютекс1);

Когда оба потока выполняются ТОЧНО ОДНОВРЕМЕННО, оба потока одновременно находятся в критической секции.

Единственным решением (должно быть на аппаратном уровне) было бы заставить каждый процессор немного работать друг с другом, но это противоречит цели параллелизма.

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

(это не проблема атомарности, а скорее проблема точного параллелизма, и мне интересно, как с этим справляется SMP).


person SHH    schedule 23.10.2011    source источник


Ответы (5)


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

Где-то в аппаратном обеспечении есть арбитр шины, который разрешает только одному ядру управлять шиной, соединяющей эти два ядра. Если какое-либо ядро ​​уже имеет память, содержащую мьютекс в частном кеше, это ядро ​​​​выиграет. В противном случае выиграет тот, кто первым доберется до автобуса.

Арбитр шины может работать по-разному, но обычно он меняется. Таким образом, если ядра 0, 1, 2 и 3, а ядро ​​2 имело шину последней, то шина затем пойдет к ядру 3, если оно этого захочет, в противном случае к ядру 0, если оно этого захочет, иначе к ядру 1, если оно этого захочет, в противном случае вернитесь к ядру 2. В зависимости от того, какая именно шина задействована (будь то битва между кэшами L2 двух ядер или из-за самой памяти или чего-то еще), некоторые ядра могут конкурировать как единое целое с другими группами ядер, а затем соперничать между собой. для которого конкретное ядро ​​получает его первым.

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

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

person David Schwartz    schedule 23.10.2011
comment
В случае SMP обычно используются многоканальные запросы к памяти, которые по-прежнему могут приводить к упомянутой выше проблеме, поэтому оба ядра будут видеть мьютекс как разблокированный, и оба ядра будут выполнять запросы к памяти, чтобы установить заблокированное значение true, что обязывает память. Кроме того, из-за кэширования значений памяти на разных ядрах основная память может вообще не вступать в игру, поэтому говорить, что другая часть оборудования обрабатывает ее, неправильно. - person Jesus Ramos; 23.10.2011
comment
@JesusRamos: оба ядра не увидят мьютекс как разблокированный, потому что они не выполняют операцию чтения, а выполняют операцию обмена. Прежде чем эта операция станет возможной, ядро ​​должно владеть строкой кэша, содержащей блокировку. Только одно ядро ​​может одновременно владеть строкой кэша. И я никогда не говорил, что основная память вступает в игру. Используемая «шина» может быть или не быть шиной памяти. Это может быть шина когерентности межъядерного кэша. - person David Schwartz; 23.10.2011
comment
Что, если два процесса имеют общие данные в кеше (как в SMP, где у каждого процессора есть свой собственный кеш)? Я предполагаю, что блокирующая шина также должна блокировать шину кэша? - person SHH; 23.10.2011
comment
@SHH: Два ядра могут иметь одни и те же общие данные в кеше каждого ядра, но в этом случае строка кеша специально помечена как «общая», и ядро ​​должно перевести ее на «исключительную», прежде чем ему будет разрешено изменить ее. Это изменит строку кэша другого ядра на «недействительную». Это реализовано от ядра к ядру и не зависит от основной памяти. (Ознакомьтесь с протоколом MESI.) Больше нет необходимости блокировать автобусы. Достаточно заблокировать строку кэша на время обмена. - person David Schwartz; 23.10.2011
comment
Значение может быть кэшировано в локальном кэше ядра, и в этом случае значение может стать устаревшим, если оно не признано недействительным, что означает, что нет необходимости выполнять арбитраж в этом кэше, поскольку только одно ядро ​​может получить к нему доступ, если только не будет создано что-то, что сделает строку кэша недействительной. Но я, возможно, только что предположил основную память, если я не прочитал ваш ответ четко. - person Jesus Ramos; 23.10.2011
comment
Думаю, теперь я понял. Таким образом, все проблемы сводятся к тому, что несколько ядер одновременно обращаются к одному и тому же расположению памяти (или кеша) и изменяют его. И в архитектуре ЦП есть какой-то способ арбитража (с помощью префикса LOCK и механизма согласования кеша), я прав? - person SHH; 23.10.2011
comment
@SHH: Точно. Чтобы быть более точным, это тот же самый физический адрес в общей памяти системы. S в SMP означает, что все процессоры видят одно и то же пространство физической памяти. - person David Schwartz; 23.10.2011

Возможно, вы захотите изучить барьеры памяти.

http://en.wikipedia.org/wiki/Memory_barrier

В этом случае блокировки будут использовать барьеры памяти, так что внутреннее значение, используемое в блокировке, не может быть доступно нескольким процессорам одновременно.

Некоторые архитектуры также позволяют блокировать все ядра, кроме 1, для этого. Например, x86 имеет префикс LOCK, который при добавлении к инструкциям блокирует доступ к памяти во время этой инструкции. (например: LOCK ADD EAX, 1 для атомарного приращения к регистру)

Архитектуры, которые не поддерживают LOCK или атомарные инструкции, используют сравнение и обмен или тестирование и установку/обмен. Обычно это включает в себя небольшой цикл инструкций, который на высоком уровне может выглядеть как

while (target != value) target = value;

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

person Jesus Ramos    schedule 23.10.2011
comment
Означает ли это, что все алгоритмы блокировки в SMP реализованы с использованием барьеров памяти (кроме атомарных инструкций)? - person SHH; 23.10.2011
comment
Судя по тому, над чем я работал в ядре и видел в компиляторах, похоже на это. Обычно при изменении этих значений вы должны аннулировать кеш на других ядрах (что связано с барьером памяти). - person Jesus Ramos; 23.10.2011
comment
Это менее ужасно вводящее в заблуждение объяснение, чем мое. @SHH: В принципе, да, любая многопроцессорная архитектура будет иметь некоторый аппаратно поддерживаемый способ выполнения некоторых операций с адресом памяти атомарно, предотвращая возникновение проблем, связанных с состоянием гонки, для этого адреса памяти в остальные ядра. Реализация ОС затем использует эти низкоуровневые функции для создания механизмов синхронизации более высокого уровня. - person millimoose; 23.10.2011
comment
@Inerdia Я не видел ничего плохого в вашем объяснении, я на самом деле собирался проголосовать за него :) Вы не вдавались в подробности, но это не значит, что с этим было что-то не так. - person Jesus Ramos; 23.10.2011
comment
@Jesus Ты опередил меня, добавив биты LOCK, так что можешь не разделять обсуждение комментариев. - person millimoose; 23.10.2011
comment
Кроме того, хотя CAS и TAS не особенно хороши для реализации блокировок общего назначения из-за потенциально длительного ожидания занятости, они полезны для написания параллельных структур данных. Если вы ожидаете, что ожидание занятости будет очень коротким (например, когда несколько потоков пытаются добавить элемент в очередь), это лучше, чем накладные расходы на использование полного мьютекса на уровне ОС. - person millimoose; 23.10.2011
comment
@Inerdia Да, вот тут-то и появляются спиновые блокировки. Обычно их используют только в условиях, когда сон невозможен, или потому, что вы ожидаете, что время ожидания будет минимальным, и в этом случае использование атомарной инструкции может фактически занять больше времени или вызов ОС. - person Jesus Ramos; 23.10.2011
comment
Барьеры памяти предотвращают переупорядочивание операций с памятью. Они не обеспечивают эксклюзивности. Префикс LOCK не блокирует инструкцию, он блокирует место в памяти. А атомарные инструкции не блокировали автобус уже более десяти лет. Впервые это было изменено в Pentium Pro, и ни один из более поздних процессоров фактически не блокирует шину. - person David Schwartz; 23.10.2011
comment
@ Дэвид Шварц Я никогда не говорил, что они заперли автобус. В SMP вам все еще требуются барьеры памяти, чтобы обеспечить правильность, которая позволила бы вам правильно получить блокировку в некоторых ситуациях. Я все еще не вижу смысла минусовать. - person Jesus Ramos; 23.10.2011
comment
@JesusRamos: голосование было понижено из-за двух основных технических неточностей. Во-первых, барьеры памяти не препятствуют одновременному доступу. Они просто предотвращают переупорядочение операций памяти относительно друг друга. Во-вторых, LOCK не останавливает выполнение другого ядра, а просто блокирует строку кэша. Он блокирует память, а не выполнение. - person David Schwartz; 23.10.2011

Я настоятельно рекомендую Системы UNIX® Курта Шиммеля для современных архитектур: симметричная многопроцессорность и кэширование для программистов ядра. Разные аппаратные архитектуры предоставляют разные низкоуровневые инструменты для синхронизации доступа к данным, в том числе некоторые архитектуры практически без помощи отсутствуют. В книге Шиммеля представлены алгоритмы, которые могут работать даже на этих архитектурах.

Я хотел бы легко найти свою копию, чтобы обобщить содержание.

person sarnold    schedule 23.10.2011

Вы можете предотвратить это, используя атомарные инструкции, такие как TLS и XCHG.

Как вы обеспечиваете атомарность инструкции?

Вы можете отключить все прерывания перед выполнением инструкции, а затем включить их все после выполнения инструкции. Это не помогает в многоядерных системах, потому что отключение прерывания в процессоре 1 никак не влияет на процессор 2. В многоядерных системах атомарность инструкции обеспечивается за счет предотвращения доступа других процессоров к шине памяти (барьер памяти) .

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

Реализация mutex_lock и mutex_unlock с использованием TSL:

 mutex_lock:
    TSL REGISTER, MUTEX ; copy mutex to register and sets mutex
    CMP REGISTER, #0    ; compare mutex to zero
    JZE ok              ; if zero return
    CALL thread_yield   ; else: mutex is busy, schedule another thread
    JMP mutex_lock      ; try again later
 ok: RET

 mutex_unlock:
    MOVE MUTEX,#0       ; free mutex
    RET

Вы можете найти некоторую информацию о TSL здесь: http://en.wikipedia.org/wiki/Test-and-set

Хорошая книга, которая может вам помочь: http://www. .amazon.com/Modern-Operating-Systems-Andrew-Tanenbaum/dp/0136006639/ref=sr

 mutex_lock:
    TSL REGISTER, MUTEX ; copy mutex to register and sets mutex
    CMP REGISTER, #0    ; compare mutex to zero
    JZE ok              ; if zero return
    CALL thread_yield   ; else: mutex is busy, schedule another thread
    JMP mutex_lock      ; try again later
 ok: RET

 mutex_unlock:
    MOVE MUTEX,#0       ; free mutex
    RET
sc_1?ie=UTF8&qid=1319333818&sr=8-1-spell

person Bruno Calza    schedule 23.10.2011
comment
Просто помните, что этот метод работает только в том случае, если ваша операция 'TSL' является атомарным тестом и набором. - person David Schwartz; 23.10.2011

Это классическая проблема взаимоблокировки. Я не уверен в аппаратной поддержке (но я почти уверен, что это поддерживается на аппаратном уровне), однако я могу привести пример решения проблемы взаимоблокировки в базах данных. Если вы знаете все зависимости, вы знаете, какую зависимость следует «убить», таким образом, команды данного узла не будут выполняться, но система преодолеет взаимоблокировку, а другие узлы не выйдут из строя. Думаю, такой же подход должен быть и на аппаратном уровне.

person Lajos Arpad    schedule 23.10.2011
comment
Этот код не будет блокироваться, один из них получит блокировку, продвинется вперед и затем освободит блокировку. Нет условий удержания и ожидания или отсутствия разблокировки. - person Jesus Ramos; 23.10.2011