Конечно, текущие стандарты C и C ++ ничего не говорят по этому вопросу.
Насколько я знаю, Posix все еще избегает формального определения модели параллелизма (хотя я могу устареть, но в этом случаеприменять мой ответ только к более ранним версиям Posix).Поэтому то, что он говорит, следует читать с небольшой симпатией - в нем точно не изложены требования в этой области, но ожидается, что разработчики «знают, что это значит» и делают что-то, что делает потоки пригодными для использования.
Когда стандарт говорит, что мьютексы «синхронизируют доступ к памяти», реализации должны предполагать, что это означает, что изменения, сделанные под блокировкой в одном потоке, будут видны под блокировкой в других потоках.Другими словами, необходимо (хотя и недостаточно), чтобы операции синхронизации включали в себя барьеры памяти того или иного типа, а необходимое поведение барьера памяти заключается в том, что он должен предполагать, что глобальные переменные могут изменяться.
Потоки не могутБыть реализованным в виде библиотеки охватывает некоторые конкретные вопросы, которые требуются для фактического использования pthreads, но не указаны явно в стандарте Posix на момент написания (2004).Становится весьма важным, согласен ли ваш компилятор или тот, кто определил модель памяти для вашей реализации, с Бемом, что означает «пригодный для использования», с точки зрения того, чтобы позволить программисту «убедительно рассуждать о правильности программы».
Обратите внимание, что Posix не гарантирует целостного кэша памяти, поэтому, если ваша реализация извращенно хочет кешировать do_something
в регистр в вашем коде, тогда , даже если вы пометили его как энергозависимый , возможно, она извращенно выберет незагрязнил локальный кеш вашего процессора между операцией синхронизации и чтением do_something
.Так что, если поток записи работает на другом ЦП со своим собственным кешем, вы можете даже не увидеть изменения.
Это (одна из причин), почему потоки не могут быть реализованы просто как библиотека.Эта оптимизация извлечения изменчивой глобализации только из локального кэша ЦП будет допустимой в однопоточной реализации C [*], но нарушает многопоточный код.Следовательно, компилятору необходимо «знать» о потоках и о том, как они влияют на функции других языков (например, за пределами pthreads: в Windows, где кэш всегда согласован, Microsoft прописывает дополнительную семантику, которую она предоставляет volatile
в несколькихрезьбовой код).По сути, вы должны предположить, что если ваша реализация столкнулась с проблемой обеспечения функций pthreads, то у нее возникнет проблема определения работоспособной модели памяти, в которой блокировки фактически синхронизируют доступ к памяти.
Если компилятор может встроить функцию и доказать, что он не имеет доступа к do_shutdown, то может ли он кэшировать do_shutdown даже в многопоточной настройке?Как насчет не встроенной функции в том же модуле компиляции?
Да для всего этого - если объект является энергонезависимым, и компилятор может доказать, что этот поток не изменяет его (либо через его имя, либо через указатель с псевдонимом), и если не возникает барьеров памяти, он может повторно использовать предыдущие значения.Конечно, могут быть и будут другие специфические для реализации условия, которые иногда останавливают его.
[*] при условии, что реализация знает, что глобальный объект не расположен по какому-то «специальному» аппаратному адресу, который требует, чтобы чтение всегда происходилочерез кэш в основную память, чтобы увидеть результаты любого аппаратного операции, влияющего на этот адрес.Но чтобы разместить глобальный объект в любом таком месте или сделать его специальным с помощью DMA или чего-либо еще, требуется волшебство, зависящее от реализации.В отсутствие какой-либо такой магии реализация в принципе может иногда знать это.