Может кто-нибудь сказать, есть ли лучший способ синхронизации в разделяемой памяти?
Определенно, да.Я бы сказал, что то, как вы тратите циклы ЦП в режиме «занят-ожидание» (while (ShmPTR->status != FILLED) ;
), уже является фатальной ошибкой.
Обратите внимание, что общая память POSIX имеет гораздо более разумный интерфейс, чем старый SysV.Подробнее см. man 7 shm_overview .
Для примитивов синхронизации есть две разные цели:
Синхронизация данных
Для защитыданные против одновременной модификации, и для того, чтобы каждый читатель получал согласованное представление данных, существует три основных подхода:
Атомный доступ
Атомный доступ требует аппаратной поддержки,и, как правило, поддерживается только для стандартных единиц измерения машинного слова (32 или 64 бита).
Мьютексы и переменные условия
Мьютексы являются взаимоисключающими блокировками.Идея состоит в том, чтобы захватить мьютекс перед проверкой или изменением значения.
Переменные условия - это в основном неупорядоченные очереди для потоков или процессов, ожидающих «условия».Библиотека pthreads POSIX включает средства для атомарного освобождения мьютекса и ожидания условной переменной.Это делает ожидание изменения набора данных тривиальным для реализации, если каждый модификатор сигнализирует или передает переменную условия после каждой модификации.
Блокировки чтения-записи.
rwlock - это примитив, который позволяет любое количество одновременных «блокировок чтения», но только одна «блокировка записи» может быть удержана на нем в любое время.Идея состоит в том, что каждый читатель захватывает блокировку чтения перед проверкой данных, а каждый писатель - блокировку записи перед ее изменением.Это работает лучше всего, когда данные чаще проверяются, чем изменяются, и механизм ожидания изменения не требуется.
Синхронизация процесса
Существуют ситуации, когда потоки и процессы должны ждать ( block ), пока не произойдет какое-либо событие.Для этого используются два наиболее распространенных примитива:
Семафоры
A Семафор POSIX - это в основном непрозрачный неотрицательный счетчик, который вы инициализируете для чего угодно (ноль)или положительное значение, в пределах, установленных реализацией).
sem_wait()
проверяет счетчик.Если он ненулевой, он уменьшает счетчик и продолжает выполнение.Если счетчик равен нулю, он блокируется, пока другой поток / процесс не вызовет sem_post()
на счетчике.
sem_post()
увеличивает счетчик.Это один из редких примитивов синхронизации, которые можно использовать в обработчике сигналов .
Барьеры
Барьер - это примитив синхронизацииблокирует до тех пор, пока в барьере не появится определенное количество потоков или процессов, затем освобождает их все сразу.
Linux не реализует барьеры POSIX (pthread_barrier_init()
, pthread_barrier_wait()
, pthread_barrier_destroy()
),но вы можете легко добиться того же, используя мьютекс, счетчик (подсчитывающий количество дополнительных процессов, необходимых для освобождения всех официантов) и переменную условия.
Существует много лучших способов реализации упомянутой пары сервер-клиент (где совместно используемая память содержит флаг и некоторые данные).
Для целостности данных и управления изменениями - мьютекс и один или двапеременные условия должны быть использованы.(Если сервер может изменить данные в любое время, достаточно одной переменной условия (changed
); если сервер должен подождать, пока клиент прочитает данные, прежде чем их изменять, потребуется две (changed
и observed
).)
Вот пример структуры, которую вы можете использовать для описания сегмента общей памяти:
#ifndef SHARED_H
#define SHARED_H
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
struct shared_data {
/* Shared memory data */
};
struct shared {
pthread_mutex_t lock;
pthread_cond_t change; /* Condition variable for clients waiting on data changes */
pthread_cond_t observe; /* Condition variable for server waiting on data observations */
unsigned long changed; /* Number of times data has been changed */
unsigned long observed; /* Number of times current data has been observed */
struct shared_data data;
};
/* Return the size of 'struct shared', rounded up to a multiple of page size. */
static inline size_t shared_size_page_aligned(void)
{
size_t page, size;
page = (size_t)sysconf(_SC_PAGESIZE);
size = sizeof (struct shared) + page - 1;
return size - (size % page);
}
#endif /* SHARED_H */
Поля changed
и observed
являются счетчиками, которые помогают избежатьокна проверки на время использования.Важно, чтобы перед обращением к разделяемой памяти поток выполнял pthread_mutex_lock(&(shared_memory->lock))
, чтобы обеспечить согласованное представление данных.
Если поток / процесс проверяет данные, он должен выполнить
shared_memory->observed++;
pthread_cond_broadcast(&(shared_memory->observe));
pthread_mutex_unlock(&(shared_memory->lock));
, а если поток / процесс изменяет данные, он должен выполнить
shared_memory->modified++;
shared_memory->observed = 0;
pthread_cond_broadcast(&(shared_memory->change));
pthread_mutex_unlock(&(shared_memory->lock));
, чтобы уведомить любых официантов.и обновите счетчики при разблокировке мьютекса.