Re[2]: sequence points
От: Dmi_3 Россия  
Дата: 09.11.05 16:05
Оценка: 222 (19) :))) :))) :))) :)
Здравствуйте, 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();
  //Передадим аргументы конструктору T
  template<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);
  }
}

УФ! Упарился! Хватит.
Надеюсь, что эти идеи помогут тем кто не гониться за объёмами кода,
а пьеса объяснит некоторым работодателям как оно в жизни бывает.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.