Как потоковые системы справляются с совместно используемыми данными, являющимися кэшируемый различными CPU?

Сначала я разработал его для разделения строк на подстроки для анализа строки, содержащей hex.
Сегодня я превратил его в сложный, но все же простой генератор.

def chunker(iterable, size, reductor, condition):
    it = iter(iterable)
    def chunk_generator():
        return (next(it) for _ in range(size))
    chunk = reductor(chunk_generator())
    while condition(chunk):
        yield chunk
        chunk = reductor(chunk_generator())

Аргументы:

Очевидные

  • iterable - это любые итерируемые / итераторы / генераторы, связывающие / генерирующие / перебирающие входные данные,
  • size - это, конечно, размер порции, которую вы хотите получить,

Более интересно

  • reductor - это вызываемый объект, который получает генератор, повторяющийся по содержание куска.
    Я ожидаю, что он вернет последовательность или строку, но я этого не требую.

    Вы можете передать в качестве этого аргумента, например, list, tuple, set, frozenset,
    или что-нибудь более причудливое. Я передал бы эту функцию, возвращая строку
    (при условии, что iterable содержит / генерирует / выполняет итерации по строкам):

    def concatenate(iterable):
        return ''.join(iterable)
    

    Обратите внимание, что reductor может вызвать закрытие генератор, возбуждая исключение.

  • condition является вызываемым объектом, который получает все, что вернуло reductor.
    Он решает утвердить & amp; вернуть его (вернув что-нибудь, оценивая True),
    или отклонить его & amp; завершить работу генератора (вернув что-нибудь другое или подняв исключение).

    Когда число элементов в iterable не делится на size, когда it исчерпан, reductor получит генератор, генерирующий меньше элементов, чем size.
    Давайте назовем эти элементы длится элементами .

    Я предложил две функции для передачи в качестве этого аргумента:

    • lambda x:x - будет получено последних элементов .

    • lambda x: len(x)==<size> - последние элементы будут отклонены.
      заменить <size>, используя число, равное size

11
задан csj 9 July 2009 в 17:52
поделиться

4 ответа

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

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

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

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

DoWork();
workDone = true;

и поток B:

while (!workDone) {}
DoSomethingWithResults()

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

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

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

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

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

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

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

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

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

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

12
ответ дан 3 December 2019 в 08:31
поделиться

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

0
ответ дан 3 December 2019 в 08:31
поделиться

В 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
ответ дан 3 December 2019 в 08:31
поделиться

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

Например,

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
}
2
ответ дан 3 December 2019 в 08:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: