Проблема
Я хочу выделить память в одном потоке и безопасно "одарить" указатель на другой поток, чтобы он мог читать эту память.
Я использую язык высокого уровня, который переводится в C. Язык высокого уровня имеет потоки (нестандартного API потоковой передачи, поскольку он кросс-платформенный - см. Ниже) и поддерживает стандартные примитивы C с несколькими потоками, такие как атомный обмен-обмен, но он не документирован (примеры использования не используются). Сложности этого языка высокого уровня:
- Каждый поток выполняет бесконечный цикл обработки событий.
- Каждый поток имеет свою собственную локальную кучу, управляемую некоторым пользовательским распределителем.
- Каждый поток имеет одну "входную" очередь сообщений, которая может содержать сообщения из любого количества других потоков.
- Очереди передачи сообщений:
- Для сообщений фиксированного типа
- Использование копирования
Теперь это нецелесообразно для больших (не хотят копирования) или переменных размера (я думаю, размер массива является частью типа). Я хочу отправить такие сообщения, и вот схема того, как я хочу это сделать:
- Сообщение (либо запрос, либо ответ) может либо хранить "полезную нагрузку" inline (скопированный, фиксированный лимит на общий размер значений), либо указатель на данные в кучке отправителя
- Содержимое сообщения (данные в куче отправителя) принадлежит потоку отправки (выделяет и освобождает)
- Принятый поток отправляет ack в отправляющий поток, когда они сделаны с содержимым сообщения.
- "Посылающие" потоки не должны изменять содержимое сообщения после отправки, до получения (ack).
- Никогда не должно быть одновременного доступа к чтению на записываемой памяти до того, как запись будет выполнена. Это должно быть гарантировано рабочим потоком очереди сообщений.
Мне нужно знать, как обеспечить, чтобы это работало без расчётов данных. Я понимаю, что мне нужно использовать ограждения памяти, но я не совсем уверен, какой из них (ATOMIC_RELEASE,... ) и где в цикле (или мне вообще нужно что-либо).
соображения о переносимости
Поскольку мой язык высокого уровня должен быть кросс-платформенным, мне нужен ответ для работы:
- Linux, MacOS и, при необходимости, Android и iOS
- с использованием примитивов pthreads для блокировки очередей сообщений:
pthread_mutex_init
иpthread_mutex_lock
+pthread_mutex_unlock
- с использованием примитивов pthreads для блокировки очередей сообщений:
- Окна
- с использованием объектов критической секции для блокировки очередей сообщений:
InitializeCriticalSection
иEnterCriticalSection
+LeaveCriticalSection
- с использованием объектов критической секции для блокировки очередей сообщений:
Если это помогает, я предполагаю следующие архитектуры:
- Архитектура ПК Intel/AMD для Windows/Linux/MacOS (?).
- Неизвестный (ARM?) для iOS и Android
И используя следующие компиляторы (вы можете предполагать "последнюю" версию всех из них):
- MSVC в Windows
- clang on Linux
- Xcode В MacOS/iOS
- CodeWorks для Android на Android
Я только что построил на Windows, но когда приложение завершено, я хочу портировать его на другие платформы с минимальной работой. Поэтому я стараюсь обеспечить совместимость между платформами с самого начала.
Попытка решения
Вот мой предполагаемый рабочий поток:
- Прочитайте все сообщения из очереди, пока он не будет пустым (только если он был полностью пуст).
- Назовите здесь "забор памяти"?
- Прочитайте содержимое сообщений (цель указателей в сообщениях) и обработайте сообщения.
- Если сообщение является "запросом", оно может обрабатываться, а новые сообщения буферизуются как "ответы".
- Если сообщение является "ответом", содержимое сообщения исходного "запроса" может быть освобождено (неявный запрос "ack" ).
- Если сообщение является "ответом", и оно содержит указатель на "ответный контент" (вместо "встроенного ответа" ), то также необходимо отправить "ответ-акс".
- Назовите здесь "забор памяти"?
- Отправлять все буферизованные сообщения в соответствующие очереди сообщений.
Реальный код слишком большой для публикации. Здесь упрощается (достаточно, чтобы показать, как осуществляется доступ к общей памяти) псевдокод с использованием мьютекса (например, очереди сообщений):
static pointer p = null
static mutex m = ...
static thread_A_buffer = malloc(...)
Thread-A:
do:
// Send pointer to data
int index = findFreeIndex(thread_A_buffer)
// Assume different value (not 42) every time
thread_A_buffer[index] = 42
// Call some "memory fence" here (after writing, before sending)?
lock(m)
p = &(thread_A_buffer[index])
signal()
unlock(m)
// wait for processing
// in reality, would wait for a second signal...
pointer p_a = null
do:
// sleep
lock(m)
p_a = p
unlock(m)
while (p_a != null)
// Free data
thread_A_buffer[index] = 0
freeIndex(thread_A_buffer, index)
while true
Thread-B:
while true:
// wait for data
pointer p_b = null
while (p_b == null)
lock(m)
wait()
p_b = p
unlock(m)
// Call some "memory fence" here (after receiving, before reading)?
// process data
print *p_b
// say we are done
lock(m)
p = null
// in reality, would send a second signal...
unlock(m)
Будет ли это решение работать? Реформируя вопрос, делает ли Thread-B "42"? Всегда, на всех рассмотренных платформах и ОС (pthreads и Windows CS)? Или мне нужно добавить другие примитивы потоков, такие как заграждения памяти?
Research
Я потратил часы, глядя на многие связанные вопросы SO, и прочитал некоторые статьи, но я все еще не совсем уверен. Основываясь на комментарии @Art, мне, вероятно, ничего не нужно делать. Я полагаю, что это основано на этом заявлении от стандарта POSIX, 4.12 Синхронизация памяти:
[...], используя функции, которые синхронизируют выполнение потоков, а также синхронизируют память по отношению к другим потокам. Следующие функции синхронизируют память по отношению к другим потокам.
Моя проблема заключается в том, что в этом предложении четко не указано, означают ли они "все доступную память" или "только память, доступная между блокировкой и разблокировкой". Я читал людей, спорящих по обоим случаям, и даже некоторые из них предполагали, что это было написано неточно специально, чтобы дать разработчикам компилятора больше свободы в их реализации!
Кроме того, это относится к pthreads, но мне нужно знать, как это относится к потоковой обработке Windows.
Я выберу любой ответ, основанный на кавычках или ссылках из стандартной документации или какого-либо другого высоконадежного источника, либо докажет, что мне не нужны заборы, либо показывает, какие ограждения мне нужны, в соответствии с вышеупомянутыми конфигурациями платформ, по крайней мере для случая Windows/Linux/MacOS. Если в этом случае потоки Windows будут вести себя как pthreads, я бы хотел получить ссылку/цитату.
Ниже приведены некоторые (из лучших) связанных вопросов/ссылок, которые я читал, но наличие противоречивой информации заставляет меня сомневаться в моем понимании.
- Включает ли pthread_mutex_lock инструкцию по сохранению памяти?
- Запоминания памяти - нужна помощь для понимания
- Проблема с проблемой синхронизации pThread
- Видимость памяти через библиотеку pthread?
- разъяснения о полных барьерах памяти, связанных с мьютексами pthread
- Спецификация модели памяти в pthreads
- http://www.hpl.hp.com/techreports/2005/HPL-2005-217R1.html
- http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_11
- https://msdn.microsoft.com/en-us/library/windows/desktop/ms684208(v=vs.85).aspx