Безопасная передача данных только для чтения в новый поток - PullRequest
11 голосов
/ 22 марта 2012

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

int ThreadParameter;

// this function runs from the main thread
void SomeFunction() {
    ThreadParameter = 5;

    StartThread(); // some function to start a thread
    // at this point, ThreadParameter is NEVER modified.
}

// this function is run in a background worker thread created by StartThread();
void WorkerThread() {
    PrintValue(ThreadParameter); // we expect this to print "5"
}

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

  1. Общий вопрос: несмотря на то, что он очень распространен, действительно ли это безопасно для всех процессорных архитектур?Как сделать это безопасным, если нет?
  2. Глобальная переменная не volatile;возможно, он будет переупорядочен после звонка StartThread() и оставит меня в покое?Как решить эту проблему?
  3. Предположим, что на компьютере есть два процессора, которые имеют свои собственные кэши.Основной поток работает на первом процессоре, а рабочий поток - на втором процессоре.Предположим, что блок памяти, содержащий ThreadParameter, был выгружен в кэш каждого процессора до того, как программа начнет работать SomeFunction().SomeFunction() записывает 5 в ThreadParameter, который сохраняется в кэше первого процессора, а затем запускает рабочий поток, который выполняется на втором процессоре.Разве WorkerThread() на втором процессоре не увидит неинициализированные данные для ThreadParameter вместо ожидаемого значения 5, поскольку страница памяти во втором процессоре еще не видела обновления от первого процессора?
  4. Если требуется что-то другое - как лучше справиться с этим, учитывая, что вместо простого int я мог бы работать с указателем на гораздо более сложные типы данных, которые не обязательно используются в многопоточной среде?

Если мои опасения необоснованны, каковы конкретные причины, по которым мне не о чем беспокоиться?

Ответы [ 3 ]

3 голосов
/ 22 марта 2012

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

(Компилятор должен гарантировать, что все записи, выполненные до запуска потока, видны в новом потоке.)

2 голосов
/ 22 марта 2012

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

Обработкаот volatile зависит от конкретного компилятора;Я знаю, что, по крайней мере, в Diab для PowerPC есть опция компилятора в отношении обработки энергозависимых: либо используйте инструкцию PowerPC EIEIO (или MBAR) после каждого чтения / записи в переменную, либо не используйте ее ... этов дополнение к запрещению оптимизации компилятора, связанной с переменной.(EIEIO / MBAR - это инструкция PowerPC о запрете переупорядочения ввода-вывода самим процессором; т. Е. Все операции ввода-вывода до выполнения инструкции должны быть выполнены до ввода-вывода после инструкции).

Из правильности/ точка зрения безопасности, это не помешает объявить его как изменчивый.Но с прагматической точки зрения, если вы инициализируете ThreadParameter достаточно далеко перед StartThread (), объявлять его энергозависимым на самом деле не нужно (и это не ускорит все последующие обращения к нему).Практически любой существенный вызов функции (скажем, к printf () или cout, или любому системному вызову и т. Д.) Будет выдавать на порядки больше инструкций, чем необходимо, чтобы гарантировать, что процессор никак не обработал бы запись вThreadParameter перед вашим вызовом StartThread ().Реально сам StartThread () почти наверняка выполнит достаточно инструкций до того, как рассматриваемый поток действительно запустится.Поэтому я полагаю, что вам не нужно объявлять его как volatile, вероятно, даже если вы инициализируете его непосредственно перед вызовом StartThread ().

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

Ваш вопрос 1 спрашивает, как сделать это безопасным для ВСЕХ процессорных архитектур.Что ж, как я уже говорил выше, вы должны быть в безопасности, если используете процессоры подобного типа, шины данных которых должным образом соединены.Процессоры общего назначения, предназначенные для межпроцессорного взаимодействия, имеют протоколы отслеживания шины для обнаружения операций записи в общую память ... при условии, что ваша библиотека потоков правильно настраивает область общей памяти.Если вы работаете во встроенной системе, вам, возможно, придется настроить это самостоятельно в своем BSP ... для PowerPC, вам нужно взглянуть на биты WIMG в конфигурации MMU / BAT;Я не знаком с другими архитектурами, чтобы дать вам указатели на них.НО .... Если ваша система является доморощенной или если ваши процессоры не похожи друг на друга, вы не сможете рассчитывать на то, что два процессора смогут отслеживать записи друг друга;проконсультируйтесь с вашими аппаратными специалистами.

1 голос
/ 22 марта 2012
  1. Да, это безопасно.
  2. Не знаю.Может быть: if( ThreadParameter = 5 ) StartThread();.Однако, в общем, старайтесь не угадывать компилятор.
  3. Вероятно, нет.Если вам приходилось беспокоиться о таких деталях низкого уровня при написании кода, то логика, управляющая тем, как программа выполняется на многоядерном компьютере, вероятно, не справляется со своей задачей.1010 * - ваш друг для работы со сложными типами в многопоточной среде.
...