Как стать автором
Обновить

Комментарии 24

А по какому принципу вы расставляете volatile? Почему, например, у поля version структуры weak_handle_s его нет? Ну и вообще зачем тут volatile, ведь как я понял из вступления, счетчик ссылок предполагается атомарным, volatile ведь тут ничем не помощник, разве нет?
Да, счётчик ссылок предполагается атомарным, и по сути он volatile. Да, Вы правы, в gcc атомарный доступ будет работать правильно при любой оптимизации и без volatile, но я не готов отвечать за другие компиляторы. Про version в weak_handle_s — это тоже верно заметили: если на совесть, там тоже должен стоять volatile.
Не совсем понятно при чем тут потоки, если описанные проблемы точно так же существуют в однопоточной среде.
Потом, непонятно, почему
«Ссылка должна оставаться залоченной пока есть вероятность что объект будет использован»

является минусом. Это нормальное поведение strong reference. И это совершенно нормально, когда ссылка должна оставаться пока есть вероятность что объект будет использован. Вы же на него ссылаетесь? Значит счетчик ссылок не 0. Если вам объект больше никогда не нужен, вы делаете
unref(ptr);
ptr = NULL;

Что бы исключить любые попытки использовать объект после освобождения. И всё.

Кстати, ваш handle не решает проблемы передачи объекта между потоками.
В однопоточной среде описанные проблемы решаются гораздо проще, а ещё в однопоточной среде гораздо проще такие проблемы не создавать в принципе.
В данном случае рассматривается применение именно в многопоточной среде, потому что именно в многопоточной среде проблема одновременного общего доступа к данным наиболее актуальна.
Хэндл решает проблему передачи объекта между потоками в том плане, что передав в поток хэндл мы всегда можем попробовать залочить объект и убедиться в том что он жив, или же понять, что объект уже умер не создавая опасных ситуаций (см. пример 1 с обращением к освобожденной памяти).
Согласен, это нормальное поведение strong reference, поэтому это минус не конкретного подхода, а в принципе техники сильных ссылок. Пожалуй я неправильно сформулировал в чём заключается минус. Минус в том что мы не можем произвольно освобождать объект на время а потом пытаться залочить вновь, если он временно не требуется.
В данном случае нет никакой разницы между однопоточной и многопоточной средой. Собственно проблема совместного владения объектами (а вы решаете именно её) довольно таки ортогональна многопоточности. По большему счету, для поддержки многопоточности надо просто решить проблему атомарности счётчика ссылок.

потому что именно в многопоточной среде проблема одновременного общего доступа к данным наиболее актуальна.

Проблема общего доступа к данным решается с помощью примитивов синхронизации (мютексов, например).

Хэндл решает проблему передачи объекта между потоками в том плане, что передав в поток хэндл мы всегда можем попробовать залочить объект и убедиться в том что он жив, или же понять, что объект уже умер не создавая опасных ситуаций

Вам надо или залочить его в первом треде или с вероятностью 50% вы получите null во втором треде. Конечно, опасной ситуации это не создаст, но и объекта вы не получите. Толку от такой надежности?
Минус в том что мы не можем произвольно освобождать объект на время а потом пытаться залочить вновь, если он временно не требуется.

Хм, а как это «временно не требуется»? Он вам либо нужен и вы держите его залоченым (это же не мютекс который нужно отпустить как можно раньше), либо он вам больше совсем не нужен и вы его отпускаете навсегда.
Проблема общего доступа к данным решается с помощью примитивов синхронизации (мютексов, например).

Я с удовольствием прочитаю если вы расскажете как решить описанную мной проблему с помощью примитивов синхронизации.
Вам надо или залочить его в первом треде или с вероятностью 50% вы получите null во втором треде. Конечно, опасной ситуации это не создаст, но и объекта вы не получите. Толку от такой надежности?

Рассмотренная ситуация является утрированной, разумеется никто и никогда так не делает. Это лишь иллюстрация того, каким образом неаккуратное использование встроенных счётчиков ссылок может легко привести к проблемам с памятью. В случае с хэндлами подобные проблемы с памятью исключены.
Хм, а как это «временно не требуется»?

Ну например объект требуется только во время каких-то операций, занимающих конечное время (чтение из сокета при наличии данных, к примеру). В остальных случаях доступ к объекту не требуется.
Я с удовольствием прочитаю если вы расскажете как решить описанную мной проблему с помощью примитивов синхронизации.

Потому что вы путаете понятие общего доступа и общего владения.
«Владение» грубо говоря определяет кто отвечает за разрушение объекта. Это та проблема, которую вы решаете в своей статье.
«Доступ» — это кто может работать с объектом в данный момент. Тут да, в полный рост вылазят проблемы именно многопоточности (хотя можно устроить проблемы с общим доступом и в однопоточной среде, да, хотя это куда сложнее). Ваша статья никак не касается проблемы общего доступа.
Рассмотренная ситуация является утрированной, разумеется никто и никогда так не делает.

Тем не менее в этой ситуации у handle нет никаких преимуществ перед обычным встроенным счётчиком ссылок. И там и там надо делать ref() в вызывающем потоке. Поэтому я не понимаю, зачем вы приводили этот пример.

Ну например объект требуется только во время каких-то операций, занимающих конечное время (чтение из сокета при наличии данных, к примеру). В остальных случаях доступ к объекту не требуется.

Ну смотрите. Если три варианта:
1) Вам нужен сокет (прямо сейчас или в будущем, не важно). Вы держите на него ссылку.
2) Сокет вам больше не нужен. Вы удаляет ссылку и забываете о нём насовсем.
3) Было бы неплохо иметь сокет, но он не обязателен. Это вариант weak refference. Вы пытаетесь взять ссылку тогда, когда он вам нужен, но вас не расстроит если сокет уже удален.

Вы предлагаете использовать ваш handle именно в режиме weak refference, т.е. отпускать ссылку сразу после использования и потом молиться, что кто-то ещё держит этот сокет, что бы вы могли взять ссылку ещё раз. Но обычно вы будете единственным владельцем сокета, и как только вы сделаете unref() — сокет будет удален. Да, потом вы сделаете ref() и получите null. У вас ничего не сломается, но и работать ничего не будет. Это нормальное поведение для weak ptr. Но на самом деле слабые ссылки нужны очень редко.
Гораздо чаще нужны именно strong ptr. И ваш handle тоже можно использовать в таком режиме. Но вы почему-то делаете упор на weak ptr и предлагаете использовать ваше handle именно так.
Тем не менее в этой ситуации у handle нет никаких преимуществ перед обычным встроенным счётчиком ссылок. И там и там надо делать ref() в вызывающем потоке. Поэтому я не понимаю, зачем вы приводили этот пример.

В данной ситуации (в первой, там где ошибка) у handle есть одно огромное преимущество: он не вызовет ошибок обращения к памяти.
Потому что вы путаете понятие общего доступа и общего владения.

Общее владение это общее владение. Общий доступ — это общий доступ. В данном случае рассматривается общий доступ к памяти. Общее владение тут указано как один из способов гарантировать сохранность выделенного участка памяти.
Вы предлагаете использовать ваш handle именно в режиме weak refference, т.е. отпускать ссылку сразу после использования и потом молиться, что кто-то ещё держит этот сокет, что бы вы могли взять ссылку ещё раз.

Я предлагаю использовать хэндл именно в режиме weak reference, т.е. ссылки на объект, а не владение объектом. В моей практике есть десятки случаев когда нужен именно weak reference.
Например, предложите мне другой способ избежать циркулярных зависимостей.
В данной ситуации (в первой, там где ошибка) у handle есть одно огромное преимущество: он не вызовет ошибок обращения к памяти.

Правильное использование handle не вызовет ошибки обращения к памяти.
Правильное использование тупого счетчика ссылок тоже не вызовет ошибок обращения к памяти.
Неправильное использование refcount(сделали get() после put()) вызовет ошибки обращения к памяти.
Неправильное использование handle (забыли проверить на null) вызовет ошибки обращения к памяти.
В чем разница?
В данном случае рассматривается общий доступ к памяти

Извините, но в данном случае не рассматривается общий доступ к памяти.
Что такое по вашему общее владение?
И что такое общий доступ к памяти?
В моей практике есть десятки случаев когда нужен именно weak reference.

Но случаев когда нужна именно strong refference всё же больше, разве нет?
Например, предложите мне другой способ избежать циркулярных зависимостей.

Ну да, циклические зависимости — это как раз то место где и нужен weak ptr. Общий на всю систему сокет — это не то место, где нужен weak ptr.
Ну да, циклические зависимости — это как раз то место где и нужен weak ptr. Общий на всю систему сокет — это не то место, где нужен weak ptr.

А сокет-то тут причём? Вы что, сокет решили счётчиками считать?
Я говорил о других объектах с которыми может идти манипуляция после вычитывания данных.
Правильное использование handle не вызовет ошибки обращения к памяти.
Правильное использование тупого счетчика ссылок тоже не вызовет ошибок обращения к памяти.

Ну вот, мы уже почти там.
Handle невозможно использовать по назначению так чтобы вызвать ошибку обращения к памяти. Если вы забыли проверить на NULL в бэктрейсе дампа вы это сразу увидите.
С тупым счетчиком ссылок это всё же возможно. При этом ошибку вы можете увидеть далеко не сразу при обращении к памяти, а гораздо, гораздо позже.
Извините, но в данном случае не рассматривается общий доступ к памяти.

Гарантия безошибочности чтения и записи в выделенный кусок памяти — это не доступ?
Проблема ABA с хэндлами — это не доступ?
Давайте не заниматься придирками и безудержным критиканством.
Но случаев когда нужна именно strong refference всё же больше, разве нет?

Как ни странно, случаев когда нужны именно strong reference у меня как раз меньше. Как правило один объект или трэд порождает объект, он им и владеет и занимается. А другие потоки им только пользуются по мере надобности.
Если вы забыли проверить на NULL в бэктрейсе дампа вы это сразу увидите.

Т.е. разница только в том, что я получу гарантированный бэктрейс? Окей.
Гарантия безошибочности чтения и записи в выделенный кусок памяти — это не доступ?

Я под безошибочным чтением подразумеваю то, что я увижу консистентное состояние объекта. Это обеспечивается или синхронизацией потоков или lock-free алгоритмами. То же самое с записью. Это да, это доступ.
А то что мы не словим segfault при чтении байта по определенному адресу — это… ну даже не знаю что это.
Проблема ABA с хэндлами — это не доступ?

Проблема ABA тут возникает из-за использования пула, который вы ввели для организации weak refference. И да, это не доступ, это проблема.
Давайте не заниматься придирками и безудержным критиканством.

Это не придирки и не критиканаство. Вы молодец что написали статью, но ваша постановка задачи неверна. И поэтому вы сделали неверные выводы в конце статьи. Я всего лишь пытаюсь донести это до вас.

Как правило один объект или трэд порождает объект, он им и владеет и занимается.

Ну хорошо. Просто мой опыт (я в основном ковыряюсь в ядре linux) говорит совершенно об обратном.
это… ну даже не знаю что это.

Ну вот это как раз одно из важнейших условий консистентности объекта.
И для того чтобы воспользоваться одним из примитивов синхронизации, тем же мьютексом, желательно сначала установить насколько он сам-то вообще живой.
но ваша постановка задачи неверна. И поэтому вы сделали неверные выводы в конце статьи

А что конкретно вам не понравилось в постановке задачи? Я от вас из критики услышал только то, что «управление циклом жизни объекта ортогонально многопоточности».
И что показалось неверным в выводах, которые, я считаю, довольно очевидны? Ну кроме, возможно, упомянутого вами минуса, который действительно как минус довольно спорен?
Ну вот это как раз одно из важнейших условий консистентности объекта.

Ну в некотором смысле да. Но это всё зависит от среды. Например в java или .net в принципе не может возникнуть этой проблемы. Если у нас есть ссылка на объект, значит объект живой (да, я знаю про Dispose() ), но тем не менее проблемы общего доступа к памяти там встают в полный рост. Поэтому как то принято отделять управление жизненным циклом объекта от собственно управления общим доступом к объекту. Эти вещи местами конечно же пересекаются (не может быть общего доступа, если объект больше/еще не существует), но тем не менее — это разные концепции.

А что конкретно вам не понравилось в постановке задачи?

Некоторый упор на многопоточность. Так же, то что вы записали в минусы подсчета ссылок «Ссылка должна оставаться залоченной пока есть вероятность что объект будет использован» хотя это не минус, а нормальное явление. Никаких лишних ресурсов это не тратит, ни к чему плохому привести не может (это же не мютекс в залоченый).
И что показалось неверным в выводах, которые, я считаю, довольно очевидны?

Опять же упор на многопоточность. И лишнее напоминание о том как плохо держать залоченую ссылку: «Если ссылка разблокирована в потоке, который имел в собственности лишь одну ссылку, этот поток уже больше не сможет гарантированно безопасно получить еще одну ссылку.». Это очевидно, как по мне и не может являеться минусом.

Я понимаю, что хендлы помогли решить какую-то вашу проблему с многопочностью. Но свою проблему вы тут не описали, а предложили эту реализацию хендлов как общее решение каких-то проблем многопоточности, которых на самом деле не существует. Посмотрите тот мой комментарий, с которого всё это началось.

Знаете, если бы вы назвали статью «Реализация сильных и слабых ссылок в C», у меня не было бы совершенно никаких вопросов. Ну кроме одного :) Как вы планируете бороться с переполнением версии?
Подобная проблема возникает при циклических зависимостях.

В C++11 для этого есть std::weak_ptr. В сети полно статей о том как это работает.
Если я правильно понял реализацию, то weak_ptr и shared_ptr используют(разделяют) специальный объект, который управляет 2мя счетчиками — weak и strong. Оригинальный обьект удаляется когда strong_count == 0, а сам объект счетчика уничтожается когда weak_count == 0. Главная идея в том, что сам shared_ptr понимает о существовании weak_ptr. При разыменовании weak_ptr проверяется значение strong_count и если оно равно 0 — попытка завершается неудачей.
Ну в некотором смысле да. Но это всё зависит от среды. Например в java или .net в принципе не может возникнуть этой проблемы.

Ну да, поэтому я и написал — в языке С.
Задача, которая решается, сформулирована в начале статьи — иметь гарантии _именно_в_многопоточном_ приложении — того, что структура или участок памяти на который мы ссылаемся — живой.
Я понимаю, что хендлы помогли решить какую-то вашу проблему с многопочностью.

Я даже ОПИСАЛ, какую. И не только хэндлы, а и интрузивные указатели тоже.
Как вы планируете бороться с переполнением версии?

Я не планирую с ней бороться. Практически при переполнении версии случай коллизии практически невероятен, если вы конечно не используете под неё unsigned char. Всё что больше этого даёт достаточно четкие гарантии.
Но поднятый вопрос понят, и, что называется acknowledged.
В данном случае нет никакой разницы между однопоточной и многопоточной средой. Собственно проблема совместного владения объектами (а вы решаете именно её) довольно таки ортогональна многопоточности. По большему счету, для поддержки многопоточности надо просто решить проблему атомарности счётчика ссылок.

Поймите меня правильно, я совсем не против того чтобы вы или кто-то другой использовал указанные примитивы для однопоточных приложений. Но в данной статье я рассматриваю их конкретно в применении к многопоточности, что и указано в заголовке.
Просто как я говорил понятие владения довольно ортогонально к многопоточности.
Что в статье есть такого (кроме «атомарного» volatile и одного натянутого примера), что имеет какое-либо отношение к многопоточности? Вы просто описали два похожих метода управления жизненным циклом объектов. Конкретно многопоточности вы коснулись только когда сказали что счётчики ссылок должны быть атомарными (а этого мало, на самом деле. Вам ещё нужен мютекс на пул).

Просто как я говорил понятие владения довольно ортогонально к многопоточности.

Давайте обойдемся без пустого критиканства, хорошо? Понятие «алгоритм» тоже «ортогонально» к понятию «программирование».
кроме «атомарного» volatile и одного натянутого примера

И без передёргивания понятий, если можно. volatile — это спецификация переменной, а «атомарный» — доступ к ним. В любом приложении хорошим тоном является обозначение с помощью volatile тех переменных которые изменяются более чем в одном потоке, и изменения которых важны для состояния.
на самом деле. Вам ещё нужен мютекс на пул

Возможно вы заметили, что вопроса организации собственно пула я не касался. И думаю что вам так же как мне известно, что давно уже существует порядочное количество lock-free алгоритмов для почти любых контейнеров.
Что в статье есть такого (кроме «атомарного» volatile и одного натянутого примера), что имеет какое-либо отношение к многопоточности?

Вот именно этот пример весьма характерен именно для многопоточных приложений. Не конкретно он, а проблема доступа к освобожденной памяти. В однопоточном приложении счётчика ссылок более чем достаточно для определения времени жизни объекта. И хэндлы в однопоточном приложении не нужны.
В однопоточном приложении счётчика ссылок более чем достаточно для определения времени жизни объекта. И хэндлы в однопоточном приложении не нужны.

Мммм, какое преимущество хэндлы имеют именно в многопоточных приложениях? Я прочитал вашу статью, комментарии, но так и не понял.
Вы о том примере с передачей объекта между потоками? Не покажете пример кода как гарантировано передать объект между двумя потоками с использованием хендлов? При условии, что в вызывающем потоке этот объект больше не нужен?
Мммм, какое преимущество хэндлы имеют именно в многопоточных приложениях? Я прочитал вашу статью, комментарии, но так и не понял.

Ну тогда извините.
Вы о том примере с передачей объекта между потоками? Не покажете пример кода как гарантировано передать объект между двумя потоками с использованием хендлов? При условии, что в вызывающем потоке этот объект больше не нужен?

Этот пример совсем не о гарантии существования объекта. Но вы уже написали, что не поняли. Чем помочь уже не знаю.
Хорошо. Итак, какое преимущество хэндлы имеют именно в многопоточных приложениях?
Давайте поставим вопрос по иному. Я не вижу никакого смысла использовать хэндлы в приложениях с одним потоком.
Забавно. А я вижу :)
Ваши хендлы действительно полезная штука. Полезная потому, что они реализуют механизм weak reference, который одинаково нужен, что в многопоточных, что однопоточных приложениях. Например для разрыва циклических ссылок ;)
В этом и есть их преимущество перед обычным reference count.

А разговоры о многопоточности — тут совершенно в кассу, извините.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории