Критические секции с многоядерными процессорами - PullRequest
22 голосов
/ 11 июня 2009

В одноядерном процессоре, где все ваши потоки запускаются из одного процессора, возникает идея реализовать критическую секцию, используя атомарную операцию проверки и установки для некоторого мьютекса (или семафора, и т. Д.) В памяти. достаточно просто; поскольку ваш процессор выполняет тестирование и установку из одного места в вашей программе, он не может выполнять одно из другого места в вашей программе под видом другого потока.

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

Кажется, что это повлечет за собой гораздо больше накладных расходов, чем случай с одним ядром, так что вот в чем суть вопроса: насколько он хуже? Это хуже? Мы просто живем с этим? Или обойти это, применяя политику, согласно которой все потоки в группе процессов должны жить в одном физическом ядре?

Ответы [ 6 ]

16 голосов
/ 11 июня 2009

Многоядерные / SMP-системы - это не просто несколько склеенных процессоров. Есть явная поддержка для параллельных действий. Все примитивы синхронизации реализованы с помощью аппаратного обеспечения по принципу atomic CAS . Инструкция либо блокирует шину, общую для процессоров и контроллера памяти (и устройств, которые выполняют DMA), и обновляет память, либо просто обновляет память, полагаясь на snooping кэша . Это, в свою очередь, заставляет алгоритм когерентность кэша заставлять все вовлеченные стороны очищать свои кэши.

Отказ от ответственности - это очень простое описание, здесь есть более интересные вещи, такие как виртуальные и физические кэши, политики обратной записи в кэш, модели памяти, заборы и т. д. и т. д.

Если вы хотите узнать больше о том, как ОС может использовать эти аппаратные средства - вот отличная книга на эту тему.

6 голосов
/ 11 июня 2009

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

Например, на чипах Intel у вас есть инструкция 'cmpxchg'. Он сравнивает значение, хранящееся в ячейке памяти, с ожидаемым значением и обменивает его на новое значение, если они совпадают. Если вам предшествует инструкция «lock», она гарантированно будет атомарной по отношению ко всем ядрам.

3 голосов
/ 11 июня 2009

Или обойти это, применяя политику, согласно которой все потоки в группе процессов должны жить на одном физическом ядре?

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

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

Есть и другие интересные темы:

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

3 голосов
/ 11 июня 2009

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

1 голос
/ 11 июня 2009

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

Более элегантное решение - использовать блок семафора HW, если процессор имеет такую ​​функцию. Семафор HW - это простая очередь, которая может иметь размер no_of_cores -1. Вот как это происходит в процессоре TI 6487/8. Вы можете либо напрямую запросить семафор (и выполнить цикл до его освобождения), либо выполнить косвенный запрос, который приведет к прерыванию, как только ваше ядро ​​получит ресурс. Запросы ставятся в очередь и обрабатываются в том порядке, в котором они были сделаны. Запрос семафора является атомарной операцией.

Непротиворечивость кэша - это еще одна проблема, и в некоторых случаях вам может потребоваться выполнить обратную запись и обновление кэша. Но это очень специфичная вещь для реализации кеша. С 6487/8 нам нужно было сделать это на нескольких операциях.

0 голосов
/ 11 июня 2009

Ну, в зависимости от того, какой тип компьютеров у вас дома, сделайте следующее: Напишите простое многопоточное приложение. Запустите это приложение на одном ядре (Pentium 4 или Core Solo), а затем запустите его на многоядерном процессоре (Core 2 Duo или аналогичном) и посмотрите, насколько велика скорость.

Конечно, это несправедливые сравнения, поскольку Pentium 4 и Core Solo намного медленнее, независимо от ядер, чем Core 2 Duo. Может быть, сравнить Core 2 Duo и Core 2 Quad с приложением, которое может использовать 4 или более потоков.

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

Что касается вашего последнего предложения о наличии всех потоков на одном физическом ядре, то это полностью разрушает точку многоядерного компьютера!

...