Давайте представим, что у меня есть несколько рабочих потоков, а именно:
while (1) {
do_something();
if (flag_isset())
do_something_else();
}
У нас есть пара вспомогательных функций для проверки и установки флага:
void flag_set() { global_flag = 1; }
void flag_clear() { global_flag = 0; }
int flag_isset() { return global_flag; }
Таким образом, потоки продолжают вызывать do_something()
в режиме busy-loop и в случае, если какой-либо другой поток установит global_flag
, он также вызовет do_something_else()
(что может, например, выводить прогресс или отладочную информацию при запросе, установив флаг из другого потока).
Мой вопрос: Нужно ли мне делать что-то специальное для синхронизации доступа к global_flag? Если да, то какова именно минимальная работа по выполнению синхронизации портативным способом?
Я пытался разобраться в этом, читая многие статьи, но до сих пор не совсем уверен в правильном ответе... Я думаю, что это один из следующих вариантов:
Нам просто нужно определить флаг как volatile
, чтобы убедиться, что он действительно считывается из общей памяти каждый раз, когда он проверяется:
volatile int global_flag;
Он может не распространяться сразу на другие ядра процессора, но рано или поздно это будет гарантировано.
Установка общего флага в одном ядре процессора не обязательно приведет к тому, что он будет виден другому ядру. Нам нужно использовать мьютекс, чтобы убедиться, что изменения флага всегда передаются путем аннулирования соответствующих строк кэша на других процессорах. Код становится следующим:
volatile int global_flag;
pthread_mutex_t flag_mutex;
void flag_set() { pthread_mutex_lock(flag_mutex); global_flag = 1; pthread_mutex_unlock(flag_mutex); }
void flag_clear() { pthread_mutex_lock(flag_mutex); global_flag = 0; pthread_mutex_unlock(flag_mutex); }
int flag_isset()
{
int rc;
pthread_mutex_lock(flag_mutex);
rc = global_flag;
pthread_mutex_unlock(flag_mutex);
return rc;
}
Это то же самое, что и B, но вместо того, чтобы использовать мьютекс с обеих сторон (reader & writeer), мы устанавливаем его только в стороне записи. Так как логика не требует синхронизации, нам просто нужно синхронизировать (аннулировать другие кэши) при смене флага:
volatile int global_flag;
pthread_mutex_t flag_mutex;
void flag_set() { pthread_mutex_lock(flag_mutex); global_flag = 1; pthread_mutex_unlock(flag_mutex); }
void flag_clear() { pthread_mutex_lock(flag_mutex); global_flag = 0; pthread_mutex_unlock(flag_mutex); }
int flag_isset() { return global_flag; }
Это позволило бы избежать непрерывной блокировки и разблокировки мьютекса, когда мы знаем, что флаг редко меняется. Мы просто используем побочный эффект мьютексов Pthreads, чтобы убедиться, что изменение распространяется.
Я думаю, что A и B - это очевидный выбор, B - более безопасно. Но как насчет C?
Если C в порядке, есть ли какой-нибудь другой способ заставить смену флага быть видимой на всех процессорах?
Есть один связанный с этим вопрос: Гарантирует ли защита переменной с помощью pthread mutex, что она также не кэшируется? ...но на самом деле это не дает ответа.