Предположим, что у меня есть несколько рабочих потоков, например:
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()
в занятом цикле, и в случае, если некоторые другие наборы потоков global_flag
, поток также вызывает do_something_else()
(который может, например, выводить ход или отладочную информацию по запросу, устанавливая флаг из другой поток).
Мой вопрос: Нужно ли мне что-то делать, чтобы синхронизировать доступ к global_flag? Если да, то какова минимальная работа для синхронизации в переносном режиме?
Я попытался понять это, прочитав много статей, но я все еще не совсем уверен в правильном ответе... Я думаю, что это одно из следующего:
A: нет необходимости синхронизировать, поскольку установка или очистка флага не создает условия гонки:
Нам просто нужно определить флаг как volatile
, чтобы убедиться, что он действительно читается из общей памяти каждый раз, когда он проверяется:
volatile int global_flag;
Он может не распространяться на другие ядра ЦП сразу, но рано или поздно гарантируется.
B: необходима полная синхронизация, чтобы гарантировать, что изменения в флагове распространяются между потоками:
Установка общего флага в одном ядре ЦП не обязательно делает его видимым другим ядром. Мы должны использовать мьютексы, чтобы гарантировать, что изменения флага всегда распространяются путем недействительности соответствующих строк кэша на других ЦП. Код становится следующим:
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;
}
C: Синхронизация необходима, чтобы убедиться, что изменения в флагове распространяются между потоками:
Это то же самое, что B, но вместо использования мьютекса с обеих сторон (читателя и писателя) мы устанавливаем его только на стороне записи. Потому что логика не требует синхронизации. нам просто нужно синхронизировать (аннулировать другие кеши) при изменении флага:
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 нормально, есть ли другой способ заставить изменение флага быть видимым на всех ЦП?
Возникает один из близких вопросов: Защищает ли переменная с помощью mutex pthread, она также не кэшируется?... но на самом деле это не так.