LINUX.ORG.RU

POSIX threads vs std / boost

 ,


0

4

Из темы Отладка ошибки многопоточности

Назовите мне хоть одну реальную причину, по которой имеет смысл использовать в проекте на C++ POSIX threads вместо std::thread / boost::thread.

Пока я вижу только две:

  • Запрет использования C++11 / C++14 / C++17 (старый компилятор, требование компании etc)
  • Старый проект, в котором уже написаны свои обёртки над POSIX thread'ами

Кто ещё в здравом уме будет скатываться до API системы, когда в языке есть более высокоуровневое средство для решения той же задачи? Это всё равно, что советовать для проектов под Windows использовать CreateThread / _beginthread / _beginthreadex вместо всё тех же std / boost.

Баги? На какие лично вы натыкались?

Производительность? Обычно проблемы производительности, связанные с синхронизацией потоков, вызваны хреновой архитектурой, а вовсе не тем, что используется std::mutex вместо pthread_mutex_t.



Последнее исправление: b0r3d0m (всего исправлений: 1)

Ответ на: комментарий от Iron_Bug

ACE
но по скорости он уделывает на корню и boost, и тем более Qt.

Сразу видно спыциалиста, лол.

anonymous
()
Ответ на: комментарий от eao197

Что значит «незаменимы»? Доказано, что они заменимы во всех случаях.

Правда?

Да.

По-твоему, здесь нужно перейти на использование CSP или чего-то подобного?

Без понятия. Если в кодовой базе уже везде используются мютексы и прочее, введение CSP может дать лишнюю когнитивную нагрузку (а может и не дать).

При том, что первый поток вообще не знает, когда именно собираемая им статистика потребуется второму потоку (может через секунду, может через час).

Не вижу проблемы, которую не решила бы try_send.

tailgunner ★★★★★
()
Ответ на: комментарий от Iron_Bug

это очень далеко от шаблонов и каких-то типизированных enum'ов.

Обычно так говорят люди, для которых естественно писать:

void do_something_withdata(const uint8_t * ptr, size_t len) {...}
и нет фантазии представить, что с шаблонами получится то же самое, но безопаснее:
void do_something_withdata(const gsl::span<uint8_t> data) {...}

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

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

Не вижу проблемы, которую не решила бы try_send.

Ну расскажи, как бы ты это делал через try_send. И к чему бы это все приводило на более низком уровне. С очередями, выделением под них памяти и пр.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

нет там места для применения шаблонов. нет никакой виртуальности. есть байты и их надо обрабатывать. и никакие span'ы там применять нельзя, иначе угробишь производительность. только мапирование памяти, только указатели, только хардкор. ни одного лишнего копирования байта не должно быть.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 2)
Ответ на: комментарий от anonymous

а ты подольше поработай и попробуй на практике разные библиотеки. потом будешь утверждать. я из практики это говорю, не просто так.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от eao197

Ну расскажи, как бы ты это делал через try_send

Серьезно? Ну тогда вот так:

out.try_send(stats);

И к чему бы это все приводило на более низком уровне. С очередями, выделением под них памяти и пр.

Зависит от того, как сделаешь. Копирование объекта в/из элемента вектора не потребует выделения памяти, передача unique_ptr - потребует выделения и освобождения.

tailgunner ★★★★★
()
Ответ на: комментарий от Iron_Bug

Ну разумеется ты тут уникум, никто кроме со всем этим не работал, ага, ага.

anonymous
()
Ответ на: комментарий от Iron_Bug

нет там места для применения шаблонов. нет никакой виртуальности

Казалось бы, какая связь между шаблонами и виртуальностью.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от Iron_Bug

Это вам 20-летний опыт подсказывает, что в любом классе будет виртуальность, а у простейшего класса span вида:

template< typename T >
class span {
  T * ptr_{};
  size_t len_{};
public :
  span() = default;
  span(T * ptr, size_t len) : ptr_(ptr), len_(len) {}
  T * ptr() const { return ptr_; }
  size_t len() const { return len_; }
  ...
};
будут какие-то накладные расходы по сравнению с ручным протаскиванием ptr/len в виде двух отдельных параметров функций?

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

Серьезно?

Более чем серьезно. Написать вызов некого абстрактного try_send я и сам могу. Ты покажи, что там под капотом будет.

eao197 ★★★★★
()
Ответ на: комментарий от anonymous

Да, не нужно было gsl приплетать, там все более продвинуто сделано. Я лишь хотел принцип проиллюстрировать.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 2)
Ответ на: комментарий от Iron_Bug

именно поэтому в системном программировании этого всего нет.

Этого всего в системном программировании нет потому, что нет желания пересмотреть свои собственные стереотипы. Вам вот кажется, что в любом классе будут виртуальные методы, а у показанного выше span есть накладные расходы, значит так оно и есть. Инженерное чутье, что уж тут поделать.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

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

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 1)
Ответ на: комментарий от eao197

Ты покажи, что там под капотом будет.

Компилябильную и бенчмаркабельную реализацию? Сейчас под рукой нет ничего, что пригодно для публикации. Но в простейшем случае - два кондвара, мютекс и буфер.

tailgunner ★★★★★
()
Ответ на: комментарий от Iron_Bug

А вот мне, почему-то, как только доводится опускаться куда-то на уровень битиков, байтиков, системных вызовов и пр., так очень часто находится, как от примитивного plain old C уйти к возможностям C++, которые ничего не стоят, но ноги отстреливать мешают. Вот, например, из совсем недавнего.

Ну и с битиками в байтиках enum class как раз очень в тему. Можно сделать так, что сам компилятор будет бить по рукам за попытку проверить не тот битик не в том поле PDU.

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

Нет, достаточно того, что ты сам пришел к пониманию, что некий разделяемый буфер с мутексом все равно у тебя будет. Только он будет запрятан под дополнительный слой абстракции, который в данном случае только усложняет сценарий использования.

Так что далеко не всегда абстракции высокого уровня полезны и упрощают жизнь разработчика.

eao197 ★★★★★
()
Ответ на: комментарий от eao197

те люди, которые кодят в ядре, давно вышли из возраста, когда себе ноги случайно отстреливают. и плюсы там уж точно никак не помогут. а вот тормозов добавят, причём ещё каких тормозов! до сокетов уровня юзерспейса кернел проводит гигантскую работу. сокет - это уже рафинированные и готовенькие данные, выданные юзеру на блюдечке с голубой каёмочкой. ты посмотри на TCP-стек в ядре. знаменитый кернельный сокет буфер (struct sk_buff) и всё, накрученное вокруг него. это и есть некоторая попытка впихнуть невпихуемое: увязать весь стек протоколов с их специфическими хэдерами и чексуммами в некую единую структуру для обмена и последовательной обработки внутри ядра. получилось сложно, очень громоздко и ракообразно, но ничего лучшего там не придумать, увы. и он ещё и периодически меняется, потому что появляются новые протоколы.

Iron_Bug ★★★★★
()
Ответ на: комментарий от eao197

Нет, достаточно того, что ты сам пришел к пониманию, что некий разделяемый буфер с мутексом все равно у тебя будет.

Я специально сказал «простейший вариант». Небуферизованный канал (классических хоаровский) можно сделать и без промежуточного буфера. Впрочем, это экономия на спичках.

Так что далеко не всегда абстракции высокого уровня полезны и упрощают жизнь разработчика.

Да, Капитан. Жизнь разработчика упрощают только хорошие абстракции (например, CSP). Но даже они в некоторых редких случаях не годятся.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от tailgunner

Но даже они в некоторых редких случаях не годятся.

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

eao197 ★★★★★
()
Ответ на: комментарий от Iron_Bug

те люди, которые кодят в ядре, давно вышли из возраста, когда себе ноги случайно отстреливают.

Не, ну как ребенок, ей Богу. Из этого возраста никогда не выходят.

eao197 ★★★★★
()
Ответ на: комментарий от tailgunner

пока что и спора-то нет. по факту, внутри ядра для обработки сети и прочих дров имеем sk_buff. но BSD-шная реализация не менее корява и точно так же не подходит под какую-либо шаблонизацию, если мы продолжаем разговор о «необходимости» плюсов в ядре. это растёт не из способа описания TCP-стека, а из структуры самого стека, которая складывалась и расширялась исторически. так что уж что есть, то есть. там много корявого, много нелогичного. но никто это не будет переделывать ради удобства программистов.

Iron_Bug ★★★★★
()
Последнее исправление: Iron_Bug (всего исправлений: 2)
Ответ на: комментарий от eao197

выходят-выходят. я не называю таких людей «гуру» или «просветлёнными». я их просто называю профессионалами. просто многие до этого уровня никогда не доходят, но это другой вопрос.

Iron_Bug ★★★★★
()
Ответ на: комментарий от Iron_Bug

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

eao197 ★★★★★
()

Обычно проблемы производительности, связанные с синхронизацией потоков, вызваны хреновой архитектурой, а вовсе не тем, что используется std::mutex вместо pthread_mutex_t.

Но при одинаковой архитектуре (плохой или хорошей — неважно), std:: будет тормозить больше.

utf8nowhere ★★★
()
Ответ на: комментарий от eao197

Но даже они в некоторых редких случаях не годятся.

Я тебе об этом сразу сказал

Я с этим и не спорил.

разделяемые данные, нуждающиеся в защите

Эта формулировка подходит для 100% задач с параллелизмом.

tailgunner ★★★★★
()
Последнее исправление: tailgunner (всего исправлений: 1)
Ответ на: комментарий от utf8nowhere

при одинаковой архитектуре (плохой или хорошей — неважно), std:: будет тормозить больше.

Может, ты еще знаешь, насколько больше?

tailgunner ★★★★★
()
Ответ на: комментарий от utf8nowhere

И у тебя, конечно же, есть пруфлинки на бенчи. Тыж не обычный 3.14добол, верно?

anonymous
()
Ответ на: комментарий от Iron_Bug

Что такое, в споре sk_buff vs mbuf уже выявлен победитель?

пока что и спора-то нет

«Пока что»? И ты якобы программируешь 20 лет? Ну-ну.

tailgunner ★★★★★
()
Ответ на: комментарий от anonymous

чтение/запись сделаны быстрыми сами по себе и без дополнительных вызовов, то и нет особой надобности именно в shared_mutex, без него даже быстрее может быть.

быстрыми, атомарными и не нарушающими всякие когерентности, с помощью волшебных гномиков вестимо

pon4ik ★★★★★
()
Ответ на: комментарий от tailgunner

Эта формулировка подходит для 100% задач с параллелизмом.

Согласен, что выразился неудачно. Но вот и со 100% задач с параллелизмом ты, вероятно, так же погорячился. Вроде как тот же Map-Reduce работает за счет того, что исходные данные можно разбить на непересекающиеся области и работать с ними совершенно спокойно. А необходимость в синхронизации возникает лишь на стадии Reduce.

eao197 ★★★★★
()
Ответ на: комментарий от pon4ik

быстрыми, атомарными и не нарушающими всякие когерентности, с помощью волшебных гномиков вестимо

С помощью сколько-нибудь грамотного подхода к архитектуре программы и пониманию ее узких мест. Например, вот только недавно студенту по его коду разъяснял. У него была задача организовать очередь сообщений, которые из разных тредов отсылались разным клиентам. Очередь у него была одна и на синхронизации он здорово терял по времени. По сути он как раз решил, что очередь она и есть очередь, нужно просто расставить блокировки (в том числе тот самый rw-lock) и задача решена. А более эффективное решение было простым - сделать несколько очередей, плюс каждый тред делает swap очереди перед отправкой, чтоб отпустить сразу блокировку и работать с локальными данными (в исходном варианте блокировка держалась все время пока отсылались сообщения). И никаких волшебных гномиков привлекать не пришлось.

anonymous
()
Ответ на: комментарий от andreyu

Когда хотят rwlock, то подразумевают некоторые гарантии защиты от starvation для reader-ов и/или writer-ов. Обычно для writer-ов: потоки не смогут получать доступ на чтение, если есть writer-ы, ждущие доступа на запись.

Есть такие гарантии для shared_lock?

utf8nowhere ★★★
()
Ответ на: комментарий от anonymous

Это именно rw-lock, с ожидаемым поведением.

Посмотрел описание методов lock() и shared_lock(). Никаких гарантий там нет.

utf8nowhere ★★★
()
Ответ на: комментарий от Iron_Bug

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

И как сюда относится std::thread? Может быть проблема как раз в велосипедистах которые криво велосипедят уже решенные проблемы?

invy ★★★★★
()
Ответ на: комментарий от anonymous

Архитектуру и подход никто не отменяет, как и синхронизацию.

Сами по себе неатомарные операции всё равно нужно блокировать.

И никаких волшебных гномиков привлекать не пришлось.

И блокировок там не было? Даже самопальных юзерсейсных? То что конкретно тут не нужен shared_lock говорит только о том, что у тебя один писатель и один читатель. Свести задачу к максимально простой конечно же надо, но бывают и натуральные случаи, когда имеет место быть множество читателей и один писатель. Не дураки же все поголовно разработчики операционных систем и прикладных api, хм.

pon4ik ★★★★★
()
Ответ на: комментарий от utf8nowhere

Эти гарантии implementation defined даже для pthreads. Чисто логически - не дело прикладного api давать гарантии об алгоритмах работы планировщика. Если найдёшь пруф, что сиё как то гарантируется в pthreads или любом другом юзерспейсном api (кроме realtime систем) очень меня удивишь.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Эти гарантии implementation defined даже для pthreads.

http://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_rwlock_rdlock.html

The pthread_rwlock_rdlock() function shall apply a read lock to the read-write lock referenced by rwlock. The calling thread acquires the read lock if a writer does not hold the lock and there are no writers blocked on the lock

Выделено специально для тебя, мой пончик.

utf8nowhere ★★★
()
Ответ на: комментарий от pon4ik

И блокировок там не было?

Были, но именно для того кода, который гарантированно быстро отрабатывал вне зависимости от того, что там делают другие треды.

Свести задачу к максимально простой конечно же надо, но бывают и натуральные случаи, когда имеет место быть множество читателей и один писатель.

Я про это уже писал. Давай на этом и сойдемся.

anonymous
()
Ответ на: комментарий от utf8nowhere

Пфф, это определение любого rw мьютекса...

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

Для ленивых:

Additionally, for object m of SharedMutex type supports another mode of ownership: shared. Multiple threads (or, more generally, execution agents) can simulataneously own this mutex in shared mode, but no thread may obtain shared ownership if there is a thread that owns it in exclusive mode and no thread may obtain exclusive ownership if there is a thread that owns it in shared mode. If more than implementation-defined number of threads (no less than 10000) hold a shared lock, another attempt to acquire the mutex in shared mode blocks until the number of shared owners drops down below that threshold.

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

говориться тоже самое только в терминах захвата ресурса, а не в терминах блокировок.

Сообщение назад ты утверждал, что гарантий ни для чего нет. Теперь ты утверждаешь, что гарантии есть. Когда ты врал?

Почитай повнимательней первый абзац про shared_mutex

Ещё раз: в описании методов lock() и shared_lock() не описано никаких гарантий, подобных предоставляемым для pthread rwlock.

utf8nowhere ★★★
()
Ответ на: комментарий от utf8nowhere

Наверное, потому что они части интерфейса объекта, который следует семантике класса этого обьекта. Дабы не дублировать слова, подразумевается, что ни один из методов обьекта не нарушает такой семантики если это не указанно явно.

Сообщение назад ты утверждал, что гарантий ни для чего нет.

Точно я?

pon4ik ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.