D_>масса "приятного" гарантирована. D_>А казалось бы чего страшного
Злой ты...
Хотя я сам запятую у себя в коде почти не использую -- даже не уверен, что в коде за последние полгода найду более 3-х использований (именно бинарной операции "запятая") -- и то в цикле for для изменения нескольких переменных -- так что мне не страшно.
Здравствуйте, Pavel Chikulaev, Вы писали:
PC>Стандартный оператор возвращает a...
Либо я не понял что Вы хотели сказать, либо Вы не правы.
a, b
1. Стандартный оператор вычисляет а, потом идет sequence point, потом вычисляет b и возвращает его
2. Переопределенный оператор вычисляет а и b (порядок вычисления не определен), потом идет sequence point, потом возвращается b.
Здравствуйте, BitField, Вы писали:
BF>Здравствуйте, Pavel Chikulaev, Вы писали:
PC>>Стандартный оператор возвращает a... BF> BF>Либо я не понял что Вы хотели сказать, либо Вы не правы. BF>a, b BF>1. Стандартный оператор вычисляет а, потом идет sequence point, потом вычисляет b и возвращает его BF>2. Переопределенный оператор вычисляет а и b (порядок вычисления не определен), потом идет sequence point, потом возвращается b.
Здравствуйте, Pavel Chikulaev, Вы писали:
PC>Здравствуйте, BitField, Вы писали:
BF>>Здравствуйте, Pavel Chikulaev, Вы писали:
PC>>>Стандартный оператор возвращает a... BF>> BF>>Либо я не понял что Вы хотели сказать, либо Вы не правы. BF>>a, b BF>>1. Стандартный оператор вычисляет а, потом идет sequence point, потом вычисляет b и возвращает его BF>>2. Переопределенный оператор вычисляет а и b (порядок вычисления не определен), потом идет sequence point, потом возвращается b.
PC>откомпиль PC>
PC>int i = 1, j = 2, k;
PC>k = i, j;
PC>cout << k;
PC>
Всё таки операттор "запятая" возвращает второй операнд, а здесь выведется единиуа, так как у оператора присваивания приоритет выше.
Здравствуйте, BitField, Вы писали:
>... так что мне не страшно
Может и не страшно...
Но часов 10 МУЧИТЕЛЬНЕЙШЕЙ ОТЛАДКИ было потрачено.
>А почем это так бьет -- по стлю, что ли?
ПЬЕСА "Кошмар программиста"
ужастик в трёх действиях без пролога(не язык) но с эпилогом.
Действие первое:
(if you cannot have the best, make the best of what you have)
Если в первом акте на стене висит ружьё
то в последнем оно выстрелит!(ну очень вольный перевод)
Рядовая контора взялась за многопоточный проект.
Блокировки частенько осуществлялись примерно так:
class lock_resource{...};
...
lock_resource(X), X.do_something();
Способ на троечку с плюсом, но прост как грабли.
Вот эти грабли и по лбу...
Действие второе:
(if you are tired then you'd better stay at home)
Увольнялся человек из конторы. (умница наверно был)
Зарплату последнюю не платили. (зачем? увольняется же)
Плюнул он на зарплату и постарался
сделать super_puper_inline operator,()
что-бы даже в DEBUG-ере туда не попадать.
А ежели и попал то выглядит как безобидное
протоколирование блокировок...
Действие третье:
(to swim like a stone)
Враг вступает в город пленных не щадя.
Потому что в кузнице небыло гвоздя!(ещё более вольный перевод)
Недели через две народ заметил неладное.
Приложение злобно глючило!
Потоки, взявшись за руки, водили хороводы
и время от времени безбожно рвали в клочья общие ресурсы!
Ещё пару недель всем миром искали ошибки.
Сначала искали использование ресурсов без блокировки.
Потом ошибки в многочисленных блокировщиках...
(сколько кода написано! А ещё говорят что
template раздувают код!)
Но больше ругали компилятор, OS, Гей Тса и матерились.
Кажется, начался "переезд" в single-thread.
Жить-то как-то надо.
Может и до сих пор матерились бы...
И это всего лишь какая-то "вшивая" sequence point!
Эпилогwelldoing)
Кстати, за исправление и советы по организации
блокировок тоже не заплатили.
"Нефиг за 30 строчек платить, у нас люди по 10000 пишут!"
Интересно, что и когда у них там опять "стрельнёт"?
А общественности дарю идеи (без реализации).
Т.к. за них не заплатили они не принадлежит конторе,
а критику услышать хочется.
template < class T, class ctritical_section = Ваша_любимая >
//Ресурс разделяемый между потоками.class shared_resource
{
typedef shared_resource<T,ctritical_section> self;
friend class lock<T,ctritical_section>;
//Может предоставлять дополнительный сервис. см. ниже
ctritical_section cs;
//Важно при истинной паралельности. Процессоры не будут "драться".
//Возможно лучше спрятать это внутри ctritical_section и обеспечить
//STATIC_ASSERT(sizeof(ctritical_section)%processor_cache_line==0);
//и размещение ctritical_section по адресам кратным processor_cache_line
//ctritical_section и data не должны попадать в одну кэш-линию.char cache_align[processor_cache_line-1];
//Без блокировки доступ к data крайне затруднён. Забыть lock невозможно.
//Можно реализовать в виде private наследования.(учитывайте cache_align)
//Возможно лучше использовать указатель ctritical_section* cs;
T data;
//А кому не лень может даже разложить всё это на стратегии.public:
//По умолчанию сгенериться не совсем то что надо.
//(И зачем Страуструп эти умолчания сделал?)
self& operator=(self const&);
explicit//template конструктор НЕ является конструктором копий.
shared_resource(self const&);
//Да! lock это smart-pointer
lock<T,ctritical_section> operator&();
shared_resource();
//Передадим аргументы конструктору Ttemplate<class A>
explicit
shared_resource(A const&);
template<class A,class B>
shared_resource(A const&,B const&);
template<class A,class B,class C>
shared_resource(A const&,B const&,C const&);
...
//Можно и кучку operator-ов добавить.operator T()const;//а НЕ operator T const&()const;void swap(self&);//Может уменьшать вероятность dead-lock см. ниже
self& operator=(T const&);
//и т.д.
...
};
//Если конструктор ресурса T использует НЕ const ссылку используйтеtemplate<class T>
class out_argument
{
T& tmp;
public:
operator T&() const throw()
{ return tmp; }
explicit out_argument(T&src) throw()
: tmp(src) {}
};
//Например если использовать конструктор you_class::you_class(std::istream&);
//то можно написать так:
shared_resource<you_class> object_name(out_argument(std::cin));
//А доступаться с блокировкой так:
(&object_name)->do_something();
access(object_name)->do_something();
template < class T, class ctritical_section = Ваша_любимая >
//Блокирует и предоставляет доступ к ресурсу.class lock{
shared_resource<T,ctritical_section>* ptr;
//Не надо его по new ! Ещё delete забудете!void* operator new(size_t);
//Попрячем это пока. Ежели понадобиться сделаем public
//Опять же не будет лёгкой возможности lock в другой поток передать.
//Вдруг я там просто счётчик инкрементирую. Ресурс то уже залочен.
lock(lock const&);
lock& operator=(lock const&);//Аналогично.public:
~lock();//Забыть unlock невозможно. И throw не страшны.
lock(shared_resource<T,ctritical_section>&);
//Не вызывайте явно: T* ptr = access(X).operator->();
//Не запоминайте указатель T*
//Внутри T::any_method() не запоминайте this;
T* operator->()const throw();
T& operator* ()const throw();//Не запоминайте ссылку!
//можно и так:
lock& operator=(class non_complete*);//Снимем блокировку так: access_ptr=0;
//далее кто как хочет и умеет:
...
};
//Ну и это для удобства. Не писать же везде lock<my_type>(my_object)template<class T>
typename lock<T>
access(shared_resource<T>&src)
{
return lock<T>(src);
}
Если в критической секции ведётся протоколирование lock\unlock
то можно детектировать ПОТЕНЦИАЛЬНЫЕ dead-lock
когда различен порядок lock (без unlock) в РАЗНЫХ потоках.
поток X: ... lock(A) ... lock(B) <dead-lock> ... unlock(B) ... unlock(A) ...
поток Y: ... lock(B) ... lock(A) <dead-lock> ... unlock(A) ... unlock(B) ...
Это, конечно, не гарантирует их полного отсутствия
но практически очень удобно, выявляет тонкие логические
несуразности и позволяет иерархически группировать
и ассоциировать ресурсы с critical_section.
В этом смысле swap можно реализовать примерно так
void swap(T&a,T&b){
//Важен одинаковый порядок блокировок.if(compare_critical_section_id(a,b){//ID могут быть просто адресами.
std::swap(a,b);
}else{
std::swap(b,a);
}
}
УФ! Упарился! Хватит.
Надеюсь, что эти идеи помогут тем кто не гониться за объёмами кода,
а пьеса объяснит некоторым работодателям как оно в жизни бывает.
Я полностью в коде не разобрался, но не лучше ли вместо перегрузки оператора & перегрузить оператор ->? О таком подходе и Александреску, и Страуструп упоминали.
Здравствуйте, Глеб Алексеев, Вы писали:
ГА>Здравствуйте, Dmi_3.
ГА>... но не лучше ли вместо перегрузки оператора & перегрузить оператор ->?
Это зависит от того хотим ли мы ЯВНО видеть потенциально долгие блокировки.
Безусловно X->do_something(); проще чем access(X)->do_something();
Перефразируя можно сказать: Делайте Ваши программы максимально простыми, но не проще чем необходимо.
Ситуация немного напоминает классическое приведение типов и _cast
Казалось бы, причем здесь KISS?
D_>Действие второе: D_>(if you are tired then you'd better stay at home)
D_>Увольнялся человек из конторы. (умница наверно был) D_>Зарплату последнюю не платили. (зачем? увольняется же) D_>Плюнул он на зарплату и постарался D_>сделать super_puper_inline operator,() D_>что-бы даже в DEBUG-ере туда не попадать. D_>А ежели и попал то выглядит как безобидное D_>протоколирование блокировок...
Из той же оперы. Казалось бы, куда проще (вместо ACE, имхо, можно взять любую библиотеку с поддержкой потоков):
ACE_Thread_Mutex lock;
...
ACE_Guard< ACE_Thread_Mutex > scoped_guard< lock >;
// Твори с ресурсом все, что захочу.
ан нет, хотелось покруче и покрасивши. Особенно при отладке. Когда выяснилось, что сопровождать сложный красивый код не кому.
D_>Действие третье: D_>(to swim like a stone) D_>Враг вступает в город пленных не щадя. D_>Потому что в кузнице небыло гвоздя!(ещё более вольный перевод)
Интересный код. Правда оценить, насколько он удобен в реальном использовании не берусь. Не приходилось мне разрабатывать классов, которые не содержали бы в себе защиты от многопоточности, но которые бы использовались бы в многопоточной программе через такие хитрые lock-обертки. Иногда, одним mutex-ом приходится защищать сразу несколько ресурсов. Иногда разрабатывается thread-safe класс, который внутри себя содержит mutex и сам захватывает его внутри собственных методов. Тогда нет проблем с тем, что результат lock::operator*() нельзя сохранять. А еще класс с собственным mutex-ом внутри может быть удобен при использовании single-writter/multi-reader блокировок. Например, const-методы захватывают mutex в read-only блокировке, а не-const методы -- в write-only блокировке.
В общем,
Simplicity does not precede complexity, but follows it.
D_>Эпилогwelldoing) D_>Кстати, за исправление и советы по организации D_>блокировок тоже не заплатили. D_>"Нефиг за 30 строчек платить, у нас люди по 10000 пишут!" D_>Интересно, что и когда у них там опять "стрельнёт"?
Здравствуйте, eao197, Вы писали:
E_>А вы, я смотрю, любите компилятор напрягать по полной программе…
Да! И это гораздо лучше, чем напрягать коллег тем, что им приходиться не забыть вызвать lock\unlock или(что лучше) создавать объект-блокировщик в функциях-членах. Приведённый вариант с template приводит к более быстрой компиляции чем код исходно использовавшийся в конторе. (Существенное время компиляции занимает ввод и разбор лексем, а их число стало на 4 (двоичных) порядка меньше.) Это экономит МОЁ время, а от того как нагреется при этом процессор я абстрагируюсь. Там где время компиляции может меня(а не компилятор) не устроить я предпринимаю специальные меры. В частности делаю программу более дружественной и защищённой от ошибок, что позволяет уменьшить частые перекомпиляции. Все-таки хочется заботиться о человеке больше, чем о железках и\или битиках программы.
E_>Из той же оперы. Казалось бы, куда проще (вместо ACE, имхо, можно взять любую библиотеку с поддержкой потоков):
E_>ACE_Thread_Mutex lock;
E_>...
E_>ACE_Guard< ACE_Thread_Mutex > scoped_guard< lock >;
E_>// Твори с ресурсом все, что захочу.
E_>ан нет, хотелось покруче и покрасивши. Особенно при отладке. Когда выяснилось, что сопровождать сложный красивый код не кому.
Я с трудом понимаю что Вы хотите сказать(не знаком с ACE) да и согласовать приведённые русские фразы для меня сложно. По моему мнению:
1) Не является правильной семантической конструкцией для блокировки (боюсь, что даже сделать это правильной синтаксической конструкцией трудно).
2) Их код трудно назвать сложным и красивым.(…прост как грабли…)
3) Они решали задачу (не вполне адекватными методами), а не пытались покруче и покрасивши. Я тоже не ставил себе такую цель. Просто, проведя анализ проблем, возникающих в их коде попытался найти более адекватное решение.
4) Отсутствие operator,() и блокировок в списке инициализаторов конструктора (что иногда нужно) равно как и ЛЮБАЯ технология в рамках C++ не поможет от злонамеренной порчи программы. Он бы использовал связку #define \ template и\или нарушение ODR. Это приведёт к аналогичным эффектам. Подробнее могу сообщить ТОЛЬКО приватно, если будет понятно, как блокируются ресурсы в Вашем проекте. Но лучше проверьте мне на слово.
5) Использование синтаксиса с operator,() вне списка инициализаторов конструктора удобно тем, что не приходиться придумывать бредовое имя для локера(с возможным warning о его неиспользуемости и исчезновением блокировок при агрессивной оптимизации) и расставлять скобки {} для снятия блокировки. Это не относится к моему варианту т.к.
а) Не нужен operator,() используется operator->()
b) Бредовое имя начинает именовать ИСПОЛЬЗУЕМЫЙ интерфейс доступа к ресурсу.
c) Вместо расстановки скобок{} можно написать. access_ptr=0; и освободить ресурс.
E_>Иногда, одним mutex-ом приходится защищать сразу несколько ресурсов. Иногда разрабатывается thread-safe класс, который внутри себя содержит mutex и сам захватывает его внутри собственных методов.
Каждый подход (даже используемый в конторе) может найти своё применение.
Бесспорным достоинством Вашего подхода является то, что E_>… нет проблем с тем, что результат lock::operator*() нельзя сохранять.
Я был бы благодарен, если Вы сообщите примеры, где это можно широко использовать.
Но, похоже, что это единственное преимущество.
//Позволяет специализировать поведение для некоторых типов.
//В частности использовать interlocked_functions и т.д.template < class T, class locker >
class shared_resource
{
//Позволяет использовать некоторые типы сторонних библиотек и встроенные типы.
T data;
//Позволяет во время исполнения ассоциировать блокировщик для ресурса.
//и легко менять стратегии блокировок.
//(one_section \ section_per_class \ section_per_object \ mutex и т.д.
locker* lock;
};
E_>А еще класс с собственным mutex-ом внутри может быть удобен при использовании single-writter/multi-reader блокировок. Например, const-методы захватывают mutex в read-only блокировке, а не-const методы -- в write-only блокировке.
Я бы (в большинстве случаев) отнёс ранний выбор стратегии захвата к недостаткам метода, препятствующим повторному использованию класса.
Как я уже говорил ситуация напоминает _cast:
//Выбираем тип доступа только при использовании, а не при написании класса.
full_access(X)->do();
//Аналог const_cast
read_access(X)->do_const();//Это read-only блокировка
//Аналог static_cast
lock_base_only<base_X>(X)->base_X::non_virtual_method();
//Аналог dynamic_cast (в смысле наличия исключения)
access_with_exception<already_locked>(X)->do();
access_with_exception<time_expered>(X,12*second)->do();
//И даже аналог reinterpret_cast
access_without_lock(X)->do();
Можно предложить и расширения, автоматически выбирающие способ доступа в зависимости от сигнатуры метода. Но всё это ТОЛЬКО если задача требует, а в простых случаях – можно просто как говорил Глеб Алексеев: X->do(); Я предпочитаю access(X)->do();
P.S.
Существуют и другие недостатки как моего, так и Вашего методов. О них могу сообщить позже.
Offtopic
С коммерческой точки зрения Ваш подход вполне оправдан. Пользователь получив готовый проект отвечающий спецификации но чем-то его не удовлетворяющий, закажет новый проект оплатив по полной программе его разработку(которую придётся проводить с середины). В моём случае изготовление второго проекта могло бы быть быстрее и обойтись дешевле (за счёт большей гибкости и повторной используемости кода), но пользователь об этом не знает, да и программистам это не выгодно.
Здравствуйте, Dmi_3, Вы писали:
E_>>А вы, я смотрю, любите компилятор напрягать по полной программе… D_>Да! И это гораздо лучше, чем напрягать коллег тем, что им приходиться не забыть вызвать lock\unlock или(что лучше) создавать объект-блокировщик в функциях-членах. /.../ Все-таки хочется заботиться о человеке больше, чем о железках и\или битиках программы.
Так-то оно так. Просто я для себя пришел к выводу, что сложный код, который использует максимум возможностей и тонкостей языка, приводит, как минимум, к двум проблемам:
1. Невозможность компиляции или неправильное поведение программы на менее "продвинутом" компиляторе. Сейчас уже не так актуально, хотя здесь проскакивают вопросы о причинах internal compiler error. А раньше это бывало сплошь и рядом на шаблонных конструкциях. Или вот еще, помню, как мне пришлось году эдак в 95-м или 96-м изменить дизайн программы из-за того, что имевшаяся в моем распоряжении версия GNU C++ под RedHat Linux не перехватывала в catch объекты-исключения производных классов. А на Symbian OS, как и сейчас, оказывается, нормальных C++ исключений нет (Программирование под Symbian OS: начало
2. Изощренно хитрые конструкции могут выглядеть красиво для меня, но совсем не для моего колеги или, что хуже, пользователя. И, что забавно, спустя какое-то время, мне самому становится тяжело в этом коде разбираться.
Поэтому сейчас я стараюсь не использовать код, который не становится понятным и очевидным после 5-10 минутного изучения. Ваш код, к сожалению, находится на грани фола. Т.е. понять его можно и без особых проблем, но для этого требуется некоторое время и напряжение. А для меня это уже показатель, что что-то не так.
E_>>ан нет, хотелось покруче и покрасивши. Особенно при отладке. Когда выяснилось, что сопровождать сложный красивый код не кому. D_>Я с трудом понимаю что Вы хотите сказать(не знаком с ACE)
The ADAPTIVE Communication Environment -- очень продвинутая библиотека для большого количества низкоуровневых, системных вещей. Жаль, что не очень популярная у нас. Хотя, к счастью, две книги про ACE на русский уже переведены (Документация по ACE
D_>4) Отсутствие operator,() и блокировок в списке инициализаторов конструктора (что иногда нужно) равно как и ЛЮБАЯ технология в рамках C++ не поможет от злонамеренной порчи программы. Он бы использовал связку #define \ template и\или нарушение ODR. Это приведёт к аналогичным эффектам. Подробнее могу сообщить ТОЛЬКО приватно, если будет понятно, как блокируются ресурсы в Вашем проекте. Но лучше проверьте мне на слово.
На форуме трудно верить на слово
Я вот думал, что привел корректный код scoped_guard, а оказалось...
D_>5) Использование синтаксиса с operator,() вне списка инициализаторов конструктора удобно тем, что не приходиться придумывать бредовое имя для локера(с возможным warning о его неиспользуемости и исчезновением блокировок при агрессивной оптимизации) и расставлять скобки {} для снятия блокировки. Это не относится к моему варианту т.к.
А вот эту тему можно раскрыть подробнее?
D_>а) Не нужен operator,() используется operator->() D_>b) Бредовое имя начинает именовать ИСПОЛЬЗУЕМЫЙ интерфейс доступа к ресурсу.
Здесь так же не совсем понятно.
D_>c) Вместо расстановки скобок{} можно написать. access_ptr=0; и освободить ресурс.
E_>>Иногда, одним mutex-ом приходится защищать сразу несколько ресурсов. Иногда разрабатывается thread-safe класс, который внутри себя содержит mutex и сам захватывает его внутри собственных методов. D_>Каждый подход (даже используемый в конторе) может найти своё применение. D_>Бесспорным достоинством Вашего подхода является то, что E_>>… нет проблем с тем, что результат lock::operator*() нельзя сохранять. D_>Я был бы благодарен, если Вы сообщите примеры, где это можно широко использовать.
Вообще-то у меня совсем другой подход. Не знаю, как широко его можно использовать, но заключается он в том, чтобы свести количество объектов, которые разделяются между потоками к минимуму. У нас вообще используется фрейморк для мультипоточного (да и не только) программирования, в котором объекты работают в собственных потоках, даже не зная о том, что есть другие потоки. А межпотоковое взаимодействие строится на обмене сообщениями. В результате работать с разделяемыми объектами приходится настолько редко, что использовать обычный scoped_lock совершенно не обременительно. Более того, его использование тривиально, хорошо понятно и заметно.
Чтобы не быть голословным, в моем проекте ObjESSty
всего пять упоминаний ACE_Guard на более чем пятьдесят тысяч строк кода. А ведь ObjESSty предоставляет и thread-safe классы. А в вышеупомянутом фреймворке -- 60 на сорок пять тысяч строк, и это притом, что там как раз ведется управление многопоточностью.
... << RSDN@Home 1.1.4 stable rev. 510>>
SObjectizer: <микро>Агентно-ориентированное программирование на C++.