Как многопоточные системы справляются с кэшированием общих данных различными процессорами? - PullRequest
9 голосов
/ 09 июля 2009

Я пришел в основном из c ++, но думаю, что этот вопрос относится к многопоточности на любом языке.Вот сценарий:

  1. У нас есть два потока (ThreadA и ThreadB) и значение x в общей памяти

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

  3. Если потоки работают на разных процессорах, что происходит, если ThreadA выполняет операцию записи, но его процессор помещает результатв его кеше L2, а не в основной памяти?Затем, если ThreadB попытается прочитать значение, не будет ли он просто смотреть в свой кэш / основную память L1 / L2, а затем работать с любым старым значением, которое там было?

Если это не такв случае, тогда как решить эту проблему?

Если это так, то что с этим можно сделать?

Ответы [ 4 ]

10 голосов
/ 09 июля 2009

Ваш пример будет работать просто отлично.

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

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

Например, если у вас есть тема A:

DoWork();
workDone = true;

И Нить B:

while (!workDone) {}
DoSomethingWithResults()

Поскольку оба выполняются на отдельных процессорах, нет гарантии, что записи, выполненные в DoWork (), будут видны потоку B до того, как запись в workDone и DoSomethingWithResults () перейдут в потенциально несовместимое состояние. Барьеры памяти гарантируют некоторый порядок чтения и записи - добавление барьера памяти после DoWork () в потоке A приведет к завершению всех операций чтения / записи, выполняемых DoWork, перед записью в workDone, так что поток B получит согласованное представление. Мьютексы по своей природе обеспечивают барьер памяти, так что чтение / запись не может передать вызов для блокировки и разблокировки.

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

1 голос
/ 09 июля 2009

Большинство блокирующих примитивов, таких как мьютексы, подразумевают барьеры памяти . Это вызывает сброс кеша и перезагрузку.

Например,

ThreadA {
    x = 5;         // probably writes to cache
    unlock mutex;  // forcibly writes local CPU cache to global memory
}
ThreadB {
    lock mutex;    // discards data in local cache
    y = x;         // x must read from global memory
}
0 голосов
/ 09 июля 2009

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

public static volatile int loggedUsers;

Другая часть представляет собой синтаксическую обертку вокруг методов .NET, которая называется Threading.Monitor.Enter (x) и Threading.Monitor.Exit (x), где x - переменная для блокировки. Это заставляет другие потоки, пытающиеся заблокировать x, ждать до тех пор, пока блокирующий поток не вызовет Exit (x).

public list users;
// In some function:
System.Threading.Monitor.Enter(users);
try {
   // do something with users
}
finally {
   System.Threading.Monitor.Exit(users);
}
0 голосов
/ 09 июля 2009

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

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