[параллель. прог] синхронизация много reader-ов, мало writer-ов.
а то есть pthread_rwlock.
То, что ты описал, соответствует шаблону "Read/Write Lock" . Могу привести код на Java, а лучше поищи где-нибудь в Google.
О, это то что нужно. Я сегодня узнал что-то новое Спасибо.
Приведи код, если не сложно
thr_rwlock.c можно посмотреть в =)
Есть в локалке в электронном виде, с примерами кода.
int my_rw_rdlock(rw_lock_t *rwp)
{
pthread_mutex_lock(&rwp->lock);
я особо не вчитывался просто =)
PPS: так что вопрос остается открытым.
по-моему, все отлично =)
Блин. Так и я придумал Мне нужно чтобы читающие треды вообще не блокировались. Понимаешь, это не проблема, когда тредов - 5. А когда их 1000 - это уже может быть проблемой.
1. Читающие и Пишущий лочатся на общем мутексе.
2. Читающие кешируют прочитанное.
3. Валидность кеша определяется по атомарному разделяемому ресурсу. Если я правильно все понимаю, атомарный ресурс не требует синхронизации.
Не, не пойдет. Читающие не могут кешировать прочитанное, так как оно всегда разное.
А чо, этого чуве не котируется?
Ну если так часто обновляется, то пиши просто в разные места. То есть имеется массифчик, и пишуший пишет сначала в первый элемент потом во второй, ... где-то хранится, какой элемент сейчас последний, читающие читают оттуда, синхронизоваться не надо, потому что в _этот_ элемент читающий больше ничего не запишет. А когда массив кончится,... ну это редко, придумай что нибудь.
здесь в главе 7.1.2 есть хороший пример о rwlock
Вот
VOID CSWMRG::WaitToRead {
// Ensure exclusive access to the member variables
EnterCriticalSection(&m_cs);
Там тоже используется кратковременная блокировка.
У месье редкостные по извращённости запросы.
Блин. Спасибо конечно, но ты прочел тред перед тем как отвечать? Мне это не подходит, уже выяснили. Мне нужно чтобы читающие треды ВООБЩЕ не блокировались.
нет ломало
И чем же мои запросы так извращены? Нормальные запросы, ИМХО. Судя по всему мне придется остановиться на первоначальной схеме.
В принципе так конечно можно сделать, но для этого придется сильно ломать структуру программы. Так как ресурс - это не просто кусок памяти. Это сложный класс, который очень накладно копировать.
Так как ресурс - это не просто кусок памяти. Это сложный классМожет быть тогда не всем читающим нужен он весь одновременно? Равно как и всем пишущим?
Разбить на более мелкие части, которые каждая защищаются отдельно?
ЗЫ А без блокировки (хотя бы очень краткосрочной) читающий поток не реализовать, потому что по крайней мере он должен проверить, что сейчас над данными не глумится пишущий.
ЗЫЫ Как вариант, заведи смарт-указатели (a la CComPtr) на защищаемый объект. Заведи helper-объект с двумя методами: "получить указатель на защищаемый объект"и "установить новый указатель на объект". Блокировка нужна только в момент замещения указателя внутри helper'а
При желании на x86 можно скопировать указатели с помощью Interlocked-функций - это весьма быстро. Получится транзакционный механизм в духе Oracle :-)
Разбить на более мелкие части, которые каждая защищаются отдельно?Это невозможно да и не нужно.
ЗЫ А без блокировки (хотя бы очень краткосрочной) читающий поток не реализовать, потому что по крайней мере он должен проверить, что сейчас над данными не глумится пишущий.Конечно не реализовать без блокировки. Но эту штуку можно реализовать без ВЗАИМОБЛОКИРОВКИ. Разницу чувствуешь?
ЗЫЫ Как вариант, заведи смарт-указатели (a la CComPtr) на защищаемый объект. Заведи helper-объект с двумя методами: "получить указатель на защищаемый объект"и "установить новый указатель на объект". Блокировка нужна только в момент замещения указателя внутри helper'аНе имеет отношения к задаче.
При желании на x86 можно скопировать указатели с помощью Interlocked-функций - это весьма быстро. Получится транзакционный механизм в духе Oracle :-)
Это невозможно да и не нужно... Не имеет отношения к задачежираф большой...
Но эту штуку можно реализовать без ВЗАИМОБЛОКИРОВКИ. Разницу чувствуешь?Как же-с не чувствовать! Чувствую, конечно. Взаимоблокировка - это deadlock по-аглицки. Без него действительно желательно обходиться :-
Нет, эту штуку можно реализовать без синхронизации читающих потоков, только если каждый из них будет синхронизироваться с пишущими потоками сам. Т.е. всё сводится к пресловутым N (ну или f(N) - для всяких компромиссных вариантов) объектам синхронизации, которые ты отмел еще в первом посте.
Если треды живые - то больше 10 смысла заводит их нет, т.к. производительность редко падает.
Если треды мертвые - то ожидание на lock-ах будет много меньше, чем работа самих тредов.
а может у него 10 процов
Defines a lock that supports single writers and multiple readers.
Use .NET, fella =)
А вообще оно элементарно реализуется через один инт и мутекс.
изначально в инте ReaderCount + 1
Ридер локает мутекс и вычитает единицу
Райтер вычитает локает мутекс и вычитает ReaderCount
если в результате операции получается 0 или меньше, то всё возвращаем на место и спим некоторое время. Повторяем.
Иначе анлокаем мутекс и делаем то что нужно.
Потом локаем, прибавляем сколько вычли, анлокаем.
А ещё я точно помню, что где-то ещё такое точно было - какой-то мутекс с интом внутри... Но не помню где.
Это не интересно.
К тому же опять все ридеры хватают один и тот же мьютекс.
Если ридеркаунт неизвестен, то подключающиеся ридеры начинают свою работу с вызова метода твоего мега-объекта, который проверит, что твой райтер не пишет, и увеличит ReaderCount на 1.
А завершаются наоборот, причём без проверки райтера на "пишет". Но с локом мутекса всё равно.
В инте под мьтексом хранится количество активных ридеров, или -1, если активен райтер.
Все, никаких ReaderCount. Никаких супер-методов мега-объектов.
чтобы читающие треды ВООБЩЕ не блокировались.Но ведь это не возможно. Что они будут делать когда наткнутся на врайтера?
Имеется ввиду чтобы читающие треды не мешали друг другу. Понятное дело что с пишущим тредом они так или иначе будут блокироваться.
То есть что бы получение ридер доступа производилось без mutexа? Так?
Нет. Чтобы лок этого мьютекса не влиял на остальные читающие треды. Если у нас есть один мьютекс на все читающие треды - это уже плохо. Я предложил решение в котором на каждый тред по одному мьютексу, и для записи надо залочить их все. Тогда читающие треды не мешают друг другу - они лочат каждый свой мьютекс.
pthread_rwlock_rdlock - читающий thread будет блокирован только в том случае, если блокировка чтения-записи установлена записывающим thread-ом. Читающие threads не будут мешать друг другу.
pthread_rwlock_wrlock - блокировка только для записи.
(pthread_rwlock_unlock - снятие блокировки любого типа)
У тебя очень много одновременных получение ридер доступа? Если так, то конечно можно сказать, что кривой дизайн, но это не ответ. То, что ты хочешь можно сделать через атомарные операции, но я не знаю существует ли релизация для userland. Даже если существует, то она не стандартна.
2Пэже. Короче, в однопроцессорном случае в кратковременном локе нет ничего страшного вообше. Какая разница, в каком порядке процессор будет делать то, что он делает, если всё равно это надо сделать? Особенно что насколько я помню освобождение мутекса может вызвать пульс - то есть непосредственную передачу управления другим тредам.
Небольшое повышения латентности запроса на чтение сосёт перед латентностью от записи врайтера.
Накладные расходы на лок всё равно будут потрачены на то, чем ты будешь синхронизироваться.
Если у тебя процессоров существенно много - штук 16 или больше - тогда эта задержка начнёт влиять, да. Но в этом случае как правило есть железные барьеры и прочая поебень, спрашивать о решении на основе стандартных осей как-то глупо.
Короче возьми бумажку, сделай правдоподобные предположения о времени выполнения своих операций, и посчитай процентный рост латентности. Его не должно быть, насколько я понимаю.
Ещё можешь попытаться подогнать "правдоподобные предположения" таким образом, чтобы процентный рост латентности был меньше чем в случае многих мутексов. Не удастся, насколько я понимаю.
Привет! Расскажи теперь почему атомарная операция - лок.
Честно говоря мне тоже так кажется. Для однопроцессорной машины это конечно не проблема, там это не лок, но для многопроцессорных - это уже лок. Мне кажется что эта атомарная инструкция много чего должна запрещать(например сообщить остальным процессорам что они не имеют права читать/писать эти область памяти)...
Вот к примеру:
c05f643c <atomic_add_int>:
c05f643c: 8b 44 24 04 mov 0x4(%esp%eax
c05f6440: 8b 54 24 08 mov 0x8(%esp%edx
c05f6444: f0 01 10 lock add %edx%eax)
c05f6447: c3 ret
имхо, это нельзя сравнивать с той блокировкой, которую вызывают слип или спин мьютексы.
взятие спинлока - это тоже одна-две операции с памятью (uncontended case сравнивать можно и нужно
rwlocks в стандартной библиотеке вполне могут быть реализованы через те же атомарные операции, кстати
А в contested - блокировка на непредсказуемое время. Как можно сравнивать гарантированное с непредсказуемым?
А если у тебя N процессоров одновременно полезут обновлять одну и ту же переменную,
какое будет время блокировки?
Есть ли вообще гарантии, что каждый процессор сможет обновить? На всех архитектурах?
При реализации rwlock через мьютексы при взятом мьютексе производится строго определённые операции,
поэтому с предсказуемостью дело обстоит ровно так же.
Кстати, на некоторых архитектурах поддержки атомарных операций в нужном объёме нет,
их реализуют через спинлоки.
А если у тебя N процессоров одновременно полезут обновлять одну и ту же переменную,Всё равно N не больше чем число процессоров.
какое будет время блокировки?
Есть ли вообще гарантии, что каждый процессор сможет обновить? На всех архитектурах?Да, при условии, что в реализации нет ошибок.
При реализации rwlock через мьютексы при взятом мьютексе производится строго определённые операции,А в линуксе не может быть так, что процесс наткнувшийся на мьютекс преемптится другим, что бы просто так не крутить процессор? Я в курсе, что в линуксе нет слиплоков. Означает ли это, что ждущий мьютекса крутится бесконечно?
поэтому с предсказуемостью дело обстоит ровно так же.
Кстати, на некоторых архитектурах поддержки атомарных операций в нужном объёме нет,Пример пожалуйста. Я вот вижу ряд архитектур, где мьютексы реализованы через атомарные операции.
их реализуют через спинлоки.
Ну а вдруг 2 процессора будут доступаться по очереди, а третьему ничего не достанется?
Откуда ты знаешь, что во всех-всех реализациях есть гарантированно справедливый доступ?
> А в линуксе не может быть так, что процесс наткнувшийся на мьютекс преемптится другим, что бы просто так не крутить процессор?
Если хочешь гарантий, придётся обеспечить невозможность такого дела.
Вне зависимости, линукс это или ещё что.
> Пример пожалуйста. Я вот вижу ряд архитектур, где мьютексы реализованы через атомарные операции.
Достаточной поддержки, это например 32-битный счётчик.
На sparc32 например проблемы с этим.
В линуксе реализованы 24-битный счётчик с помощью спинлока.
Такого быть не может. Как только процессор завершит инструкцию, которая под инструкцией lock, то все остальные процессоры сразу получат доступ к памяти.
> Откуда ты знаешь, что во всех-всех реализациях есть гарантированно справедливый доступ?
Не знаю я про все-все реализации. Я говорю про i386.
> > А в линуксе не может быть так, что процесс наткнувшийся на мьютекс преемптится другим, что бы просто так не крутить процессор?
> Если хочешь гарантий, придётся обеспечить невозможность такого дела.
Вот я и предлагаю её обеспечить, путем не использования мьютексов. И это именно то, что просит Антон.
> > Пример пожалуйста. Я вот вижу ряд архитектур, где мьютексы реализованы через атомарные операции.
> Достаточной поддержки, это например 32-битный счётчик.
> На sparc32 например проблемы с этим.
> В линуксе реализованы 24-битный счётчик с помощью спинлока.
Который в свою очередь сделан с помощью атомарных оперций над словами длины, которую поддерживает данный процессор.
> доступ к памяти.
Хватит тупить.
Какой из них, если таких несколько?
> Не знаю я про все-все реализации. Я говорю про i386.
Отвечаешь за все чипсеты, поддерживающие больше двух процессоров?
Приведи ссылку на мануал хотя бы к одному.
> Вот я и предлагаю её обеспечить, путем не использования мьютексов. И это именно то, что просит Антон.
...
> Который в свою очередь сделан с помощью атомарных оперций над словами длины, которую поддерживает данный процессор.
Вот мы и договорились, что принципиальной разницы между этими реализациями с точки зрения низкоуровневых операций не будет.
Мьютексы всегда реализованы какими-то атомарными операциями, которые и обеспечивают блокировку.
Сначала ты прекращай тупить.
> Какой из них, если таких несколько?
Задавай вопросы понятнее. Обходись без местоимений.
> Отвечаешь за все чипсеты, поддерживающие больше двух процессоров?
С каких пор чипсет выполняет то, что написано в спецификации на процессор?
> от мы и договорились, что принципиальной разницы между этими реализациями с точки зрения низкоуровневых операций не будет.
Мы не договорились. Атомарная операция блокирует на определенное число тактов, а мьютекс на неопределенное.
> Мьютексы всегда реализованы какими-то атомарными операциями, которые и обеспечивают блокировку.
Блокировку обеспечивает кручение в цикле (спинмьютекс либо преемпция другим процессом (слипмьютекс а не атомарные операции.
есть процессоры - они постоянно обращаются к одной и той же памяти через lock
какие есть гарантии, что все три процессора будет одиннаково загружены?
а не только, например, первый?
> Задавай вопросы понятнее. Обходись без местоимений.
Как будет распределятся процессорное время между 3 (4, 10 и т.д.) процессорами, если на них на всех загрузить программу вида:
какие есть гарантии работы? зависит ли это от чипсета?
c05f643c: 8b 44 24 04 mov 0x4(%esp%eax
c05f6440: 8b 54 24 08 mov 0x8(%esp%edx
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
c05f6444: f0 01 10 lock add %edx%eax)
Ты когда нибудь такой код видел? Обычно между lock операциями еще много-много-много другого кода. У тебя несуществующий вырожденный случай.
Если хочешь говорить о среднем времени ожидания, то приводи другие аргументы.
Блиа, вот ты тупишь, а?
Атомарная операция блокирует на неопределённое число тактов, если процессоров много, и все лезут в одну и ту же память. Тебе привели пример. Более того, я не поручусь за то, насколько велик кусок "одной и той же памяти", в случае с мутексом он по крайней мере точно ограничен этим мутексом.
Едем дальше. Удачный лок держится не более чем одну инструкцию. В коде для семафора удачный лок мьютекса для запроса на чтение тоже держится не более чем К инструкций. Ровно столько, во сколько компилируется соответствующий код уменьшения/увеличения счётчика. "К" не зависит от числа процессоров и количества тредов. Поэтому я назвал "К" _накладными_расходами_на_блокировку_. Поведение системы в целом никак качественно не зависит от того, у нас К инструкций или одна тратится.
Тупишь ты.
> Атомарная операция блокирует на неопределённое число тактов, если процессоров много, и все лезут в одну и ту же память. Тебе привели пример.
Такого как в примере не бывает. Дискуссия будет продолжена после приведения какой нибудь осмысленной программы, которая после компиляции содержит в коде две lock инструкции подряд.
Удачный лок держится не более чем одну инструкцию. В коде для семафора удачный лок мьютекса для запроса на чтение тоже держится не более чем К инструкций. Ровно столько, во сколько компилируется соответствующий код уменьшения/увеличения счётчика. "К" не зависит от числа процессоров и количества тредов. Поэтому я назвал "К" _накладными_расходами_на_блокировку_. Поведение системы в целом никак качественно не зависит от того, у нас К инструкций или одна тратится.Повторю еще раз. Когда тред ждёт на мьютексе, то он может (и часто именно так и происходит) быть отложен в пользу другого треда, который может сейчас выполняться. Когда скедулер вернется к отложенному треду не гарантируется ничем.
Повторю еще раз. Когда тред ждёт на мьютексе, то он может (и часто именно так и происходит) быть отложен в пользу другого треда, который может сейчас выполняться. Когда скедулер вернется к отложенному треду не гарантируется ничем.А разве в винде порядок не гарантирован, т.е. тот тред, что первый мьютекс запросил, первым и получит?
А разве в винде порядок не гарантирован, т.е. тот тред, что первый мьютекс запросил, первым и получит?Это не противоречит тому, что он может ждать на мьютексе неопределенное время.
Посмотри в словаре, что такое гарантия.
> Дискуссия будет продолжена после приведения какой нибудь осмысленной программы, которая после компиляции содержит в коде две lock инструкции подряд.
Повторяю в хз какой раз:
не нужно двух инструкций подряд, а нужно N (N>=2) процессоров, каждый из которых иногда
выполняет lock инструкцию, а все вместе они непрерывно занимают ячейку памяти в течение
неопределённо долгого времени.
Так что (N+1)-ому процессору не достаётся доступа.
Учитывая, что синхронизация доступа к памяти может занимать много тактов процессора,
то при достаточно большом N вызов атомарных операций для достижения описанного эффекта
нужно делать не так уж часто.
> Когда тред ждёт на мьютексе, то он может (и часто именно так и происходит) быть отложен в пользу другого треда, который может сейчас
> выполняться. Когда скедулер вернется к отложенному треду не гарантируется ничем.
В рассматриваемых ОС тред может быть прерван в любое время, независимо от используемых блокировок.
Когда выполнение треда продолжится, не гарантируется ничем (если не рассматривать RT-фичи).
Если же говорить про контекст ядра, то в нём легко оформить критическую секцию (в случае Linux+preempt например
а то и вообще ничего не делать не надо.
Ну говорю же я тебе, тут разница только в константах. Если у тебя используется лок на одну инструкцию, и он вызывается раз в 1000 инструкций, то тебе достаточно 1000 тредов, чтобы получить потенциально неисполняющийся тред.
Если ты используешь мьютекс, который локается/анлокается не более чем за 20 инструкций, и вызывается раз в 20000 инструкций, то тебе нужны те же самые 1000 тредов.
Какая нах разница?
Оставить комментарий
Julie16
Постановка задачи: есть ресурс, есть N читающих тредов, есть 1 пишущий тред. Вопрос: как сделать так, чтобы читающие треды могли обращаться к ресурсу без взаимоблокировки, но блокировальсь бы с пишущим тредом. В принципе можно завести N мьютексов, и каждый читающий тред должен лочить свой мьютекс, а пишущий - все мьютексы. Но это решение мне не подходит, так как N может меняться, и при этом придется сильно переделывать интерфейс(как минимум уметь регестрировать треды для ресурса и передавать для каждой функции, читающий ресурс, идентификатор треда). В общем, инетерсует, есть ли элегантное/простое решение этой задачки.