Давайте представим, что у меня есть несколько рабочих потоков, таких как:
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 в порядке, есть ли другой способ заставить изменение флага быть видимым на всех процессорах?
Есть один несколько связанный вопрос: Гарантирует ли защита переменной с помощью мьютекса pthread, что она также не кэшируется? ... но на самом деле это не отвечает.