Сотрудничество потоков на двухпроцессорных машинах - PullRequest
1 голос
/ 03 апреля 2011

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

static int i = 10;

main() {
    new Thread(thread_run1).start();
    new Thread(thread_run2).start();
    waitForThreads();
    print("The value of i: " + i);
}

thread_run1 {
    i++;
}

thread_run2 {
    i--;
}

Затем профессор спросил, каково значение i после миллиона миллиардов пробежек.(Если это вообще что-то отличное от 10). Учащиеся, незнакомые с многопоточными системами, отвечали, что в 100% случаев оператор print() всегда будет сообщать i как 10.

Это было вфакт неверный, так как наш профессор продемонстрировал, что каждое утверждение приращения / убывания было фактически скомпилировано (в сборку) как 3 утверждения:

1: move value of 'i' into register x
2: add 1 to value in register x
3: move value of register x into 'i'

Таким образом, значение i может быть 9, 10 или 11.(Я не буду вдаваться в подробности.)

Мой вопрос:

Насколько я понимаю, набор физических регистров зависит от процессора.При работе с двухпроцессорными машинами (обратите внимание на разницу между двухъядерным и двухпроцессорным процессами), имеет ли каждый ЦП свой набор физических регистров? Я предположил, что ответ - да.

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

Если быть более точным - если вы выполняли это на 8-процессорной машине, каждый ЦП имел один потокусловия гонки устранены?Если вы расширите этот пример для использования 8 потоков на двухпроцессорной машине, каждый из которых имеет 4 ядра, увеличится или уменьшится потенциал условий гонки? Как операционная система предотвращает одновременное выполнение step 3 инструкций по сборке на двух разных процессорах?

Ответы [ 3 ]

1 голос
/ 03 апреля 2011

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

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

Есть функция операционной системы, позволяющая этим программам в любом случае хромать, а не зависать в течение минут,Вызывается «привязка к процессору», доступно как параметр командной строки AFFINITY для start.exe в Windows, SetProcessAfinityMask () в winapi.Просмотрите класс Interlocked для вспомогательных методов, которые атомарно увеличивают и уменьшают переменные.

1 голос
/ 03 апреля 2011

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

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

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

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

1 голос
/ 03 апреля 2011

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...