Я просматриваю раздел 4.11 документа Основных спецификаций Open Group, выпуск 7 (IEEE Std 1003.1, 2013 Edition), раздел 4.11 документа, в котором изложены правила синхронизации памяти. Это наиболее конкретный стандарт POSIX, который мне удалось найти для детализации модели памяти POSIX/C.
Вот цитата
4.11 Синхронизация памяти
Приложения должны гарантировать, что доступ к любой области памяти для более чем одного потока управления (потоков или процессов) ограничен таким образом, что ни одна нить управления не может читать или изменять ячейку памяти, в то время как другая нить управления может изменять ее. Такой доступ ограничивается с помощью функций, которые синхронизируют выполнение потока, а также синхронизируют память по отношению к другим потокам. Следующие функции синхронизируют память с другими потоками:
fork() pthread_barrier_wait() pthread_cond_broadcast() pthread_cond_signal() pthread_cond_timedwait() pthread_cond_wait() pthread_create() pthread_join() pthread_mutex_lock() pthread_mutex_timedlock()
pthread_mutex_trylock() pthread_mutex_unlock() pthread_spin_lock() pthread_spin_trylock() pthread_spin_unlock() pthread_rwlock_rdlock() pthread_rwlock_timedrdlock() pthread_rwlock_timedwrlock() pthread_rwlock_tryrdlock() pthread_rwlock_trywrlock()
pthread_rwlock_unlock() pthread_rwlock_wrlock() sem_post() sem_timedwait() sem_trywait() sem_wait() semctl() semop() wait() waitpid()
(исключения из требования опущены).
По сути, перефразируя приведенный выше документ, правило состоит в том, что когда приложения читают или изменяют область памяти, в то время как другой поток или процесс может ее изменить, они должны обязательно синхронизировать выполнение потока и память с по отношению к другим потокам, вызвав одну из перечисленных функций. Среди них упоминается pthread_create(3)
, обеспечивающий синхронизацию памяти.
Я понимаю, что в основном это означает, что должен быть какой-то барьер памяти, подразумеваемый каждой из функций. (хотя стандарт, похоже, не использует эту концепцию). Так, например, возвращаясь из pthread_create()
, мы гарантируем, что изменения памяти, сделанные этим потоком до вызова, появятся в других потоках (возможно, с другим процессором/ядром) после того, как они также синхронизируют память. Но как насчет только что созданного потока - подразумевается ли барьер памяти перед тем, как поток начнет выполнять функцию потока, чтобы он безошибочно видел изменения памяти, синхронизированные pthread_create()
? Это предусмотрено стандартом? Или мы должны явно обеспечить синхронизацию памяти, чтобы иметь возможность доверять правильности любых данных, которые мы читаем в соответствии со стандартом POSIX?
Частный случай (который как частный случай отвечает на поставленный выше вопрос): обеспечивает ли переключение контекста синхронизацию памяти, то есть когда выполнение процесса или потока запускается или возобновляется, синхронизируется ли память по отношению к любой синхронизации памяти посредством другие потоки исполнения?
Пример:
Поток #1
создает постоянный объект, выделенный из кучи. Поток #1
создает новый поток #2
, который считывает данные из объекта. Если предположить, что новый поток #2
запускается с синхронизированной памятью, то все в порядке. Однако, если ядро ЦП, выполняющее новый поток, имеет копию ранее выделенного, но отбрасывает данные в своей кэш-памяти вместо нового значения, то у него может быть неправильное представление о состоянии, и приложение может работать некорректно.
Более конкретно...
Ранее в программе (это значение в кэш-памяти ЦП №1)
int i = 0;
Поток
T0
работает на CPU #0:pthread_mutex_lock(...); int tmp = i; pthread_mutex_unlock(...);
Поток
T1
работает на ЦП №1:i = 42; pthread_create(...);
Недавно созданный поток
T2
, работающий на CPU #0:printf("i=%d\n", i); /* First step in the thread function */
Без барьера памяти, без синхронизации памяти потока T2
может случиться так, что вывод будет
i=0
(ранее кэшированное, несинхронизированное значение).
Обновление: многие приложения, использующие библиотеку потоков POSIX, не были бы потокобезопасными, если бы эта безумная реализация была разрешена.
pthread_create
синхронизирует только вызывающий поток. Хотя было бы безумием предполагать, что вновь созданному потоку требуется синхронизация памяти, прежде чем он сможет надежно получить доступ к каким-либо данным. - person FooF   schedule 12.03.2014