LINUX.ORG.RU

Снова о статической типизации

 ,


0

4

Пример задачи:

Имеется некоторая структура данных, доступ к которой в многопоточном окружении защищен мьютексом.

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

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

Легко сообразить, что данная задача решаема на статически типизированном языке. (С разным уровнем костыльности-некостыльности, в зависимости от того, что это за язык.) И нерешаема на динамически типизированном языке.

Усложненный вариант задачи.

В программе имеются мьютексы m1, m2… mN.

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

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

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

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

Так это интерпретатор vs компилятор. Тебе вон на ЛИСПе пример привели, там динамика и компилятор. Всё прекрасно ловится.

Тут скорее проблема не в статике/динамике. А в том, что функция может быть доорпеделена позднее. В т.ч. и в статическом языке модуль может вызывать функцию которую не знает, это скомпиляется без особых проблем. И пока ты не начнёшь линковать проблем не будет. А если ты подгружаешь функцию динамически, то проблем не будет до запуска.

no-such-file ★★★★★
()
Последнее исправление: no-such-file (всего исправлений: 2)
Ответ на: комментарий от wandrien

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

zurg
()
Ответ на: комментарий от wandrien

Разваливайте.

Ок.

bar(); /* compile-time error */

С чего бы тут ошибка? Функции bar() не существует? Где это указано? А lck.bar() существует? Где это указано? При том bar() и lck.bar() - это просто разные функции.

lck->bar(); /* compile-time error */

Обращаемся к объекту, которого нет в данной области видимости. При чём тут статическая типизация? Ну и обращаемся почему-то с синтаксисом указателя. Если бы этот указатель был ранее создан, то проблем на этапе компиляции могло бы не быть.

Далее вспоминаем, что мы говорим о потоках. Соответственно, вызываем очищенную от ошибочных участков Foo::foo() одновременно много раз из разных потоков. Компиляция пройдёт успешно. Ошибка останется или нет?

Kogrom
()
Ответ на: комментарий от ac130kz

Обе, там статически в конкретное время гарантируется состояние.

Я пока не вижу, как гарантировать отсутствие в рамах одного потока параллельной конфликтующей ветки блокировки.

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

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

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

Не увидел ни в сообщении intelfx, ни в твоём сообщении ни «нормальности», ни «по-инженерному».

Неча на зеркало пенять, коли рожа крива.

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

То есть иными словами можно переформулировать проблему так:

Есть ли в раст тайп-чек, который работает на уровне цепочки (statement) -> (statement) -> (statement).

Если есть, то прямой перенос такого тайпчека на С++ невозможен – возможна только его эмуляция как method().method().method() или type<type<type>>.

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

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

В конце-концов, исполнением процессов и субпроцессов управляет операционная система, функции для работы мьютексов содержатся в API OS, что ты там собрался компилятором вычислять?

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

Я тебе выше от анона писал и повторю: да почитай ты, поучись. Ахинея на 100 сообщений полной безграмотности. Уже умные люди всё решили. Но чтобы нормально пользоваться, надо иметь представление.

masterOf
()
Ответ на: комментарий от wandrien

да есть, сам по себе тайпстейт и в плюсах мутится, но в рачте есть дополнительно фишка в виде полноценного мувинга(вот эта самая аффинная типизация), т.е. недоступность «поглощённого» какой-нибудь функцией стейта проверяется в компилтайме

zurg
()
Ответ на: комментарий от wandrien

А зачем? Что это даст? Ты же в архитектуру нос суёшь. Какая разница на чём ты её писать будешь?

Ещё раз. Это не код. Это не байтотрах. ЭТо понимание неких наборов абстракций и соглашений.

masterOf
()
Ответ на: комментарий от wandrien

Есть ли в раст тайп-чек, который работает на уровне цепочки (statement) -> (statement) -> (statement).

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

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

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

Почему?

Если вводим тип «захваченный мьютекс» и функцию, которая его захватывает и возвращает, то в других функциях можем требовать аргумент с типом «захваченный мьютекс». Чтобы потом он был гарантированно отпущен, можно сделать функцию которая выполнит защищённое мьютексом и отпустит его:

withMutex :: LockedMutex -> (LockedMutex -> IO a) -> IO a
withMutex m f = do
  r <- f m
  unlock m
  return r
monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от praseodim

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

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

Что помешает конструкции вида if (a) { открыть файл} или {захватить мьютекс} ? Причем условие a - зависит от входных данных. В этом даже логика будет, например, в зависимости от входных данных нам надо или не надо запускать параллельно какие-то виды обработки.

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

Можно конечно представить, что объявление мьютекса сделано примерно как объявление типа и часть функций для своего объявления требует иметь объявленный мьютексный тип. Это можно попробовать даже в рамках ООП соорудить.

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

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

Открытый файл или захваченный мьютекс надо передать аргументом в следующую функцию. А статическая типизация не даст вместо мьютекса передать что-то ещё.

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

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

ща спою! Сейчас покажу (всё таки.. собирался вчера, но не собрался).. правда надо это всё вспомнить и сгруппировать.. наверное несколько часов уйдёт.

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

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

Для каждого вида захваченного мьютекса можно описать отдельный тип. Это гарантирует правильный мьютекс.

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

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

В Rust, скорее всего, можно гарантировать, что сохранить в переменную нельзя.

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

В лиспах можно сделать макрос, который будет следить за мьютексом.

То есть, что-то типа

(defmutexfun (m1) bar (a b)
  ...)

(defun (foo a b)
  (bar a b) ; ошибка
  (with-mutex m1
    (bar a b)) ; работает
  (bar a b)) ; ошибка, bar -- макрос, при раскрытии проверяющий доступность  m1

Записать в переменную мьютекс нельзя, так как значение мьютекса видно только в раскрытии with-mutex, а имена мьютексов типа m1 существуют только при компиляции.

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

Там предлагают для изменения одного элемента весь массив скопировать в новое значение.

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

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

Там предлагают для изменения одного элемента весь массив скопировать в новое значение.

persistent data structures в кложе делают магию, в результате вы не будете копировать весь массив, но получите новый вход в старый + заплатку. Но, честно скажу, не разбирался как это всё внутри устроено и работает.

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

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

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

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

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

Плюс тормоза

Это есть, да.

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

Ну, тут уж нужно выбирать, что важнее. Либо закупаешься мощным железом, чтобы компенсировать тормоза автоматики, либо выбираешь C++ и уезжаешь в дурку на большой белой машине.

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

Для каждого вида захваченного мьютекса можно описать отдельный тип.

Что, очевидно, приводит к code-bloat.

Это гарантирует правильный мьютекс.

Если бы:

class MyClass {
private:

   // note - the whole thing is as private as it could be
   Mutex mutex_;

   class MyLockHolder {
      MyLockHolder(MyClass&) { // lock mutex_; }
      ~MyLockHolder() { // unlock mutex_; }
   };

   void do_something_locked(const MyLockHolder&) {...}

   void do_something() {
      const MyLockHolder lock(*this);
      do_something_locked(lock);
   }

   void do_something_bad(MyClass& other) {
      const MyLockHolder badLock(other);
      do_something_locked(badLock);
   }
};

И абсолютно ничего Вы с этим в compile time сделать не сможете в отсутствие языковых средств позволяющих отличать this от остальных указателей.

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

в отсутствие языковых средств позволяющих отличать this от остальных указателей.

не хотите внешнего вызова метода - делайте его приватным. собссно для этого спецификации видимости и сделаны.

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

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

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

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

Мы оба понимаем что это ничем не лучше?

Нет, не понимаем.

По дури и член сломать можно.

Ты прекратишь решать «любую другую задачу, кроме той, что рассматривалась в ОП», или ты это делаешь специально?

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

сделать не сможете в отсутствие языковых средств позволяющих отличать this от остальных указателей.

Давай еще скажем, что compile-time проверки не нужны, потому что они не защищают от ошибок вида «написал равно вместо не равно», «сравнил не с той переменной», «недописал алгоритм и ушел бухать».

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

const MyLockHolder badLock = other.get_lock();

class MyClass {
public:
    ...
private:
    class LockedContext {
    public:
        LockedContext(MyClass *_that) : that(_that) { /* acquire lock */ }
        ~LockedContext() { /* release lock */ }
        void do_something() {
            that->...
        }
    private:
        LockedContext(const LockedContext&) = delete;
        LockedContext(LockedContext&&) = delete;
        LockedContext& operator = (const LockedContext&) = delete;
        LockedContext& operator = (LockedContext&&) = delete;
        LockedContext* operator &() = delete;

        MyClass *that;
    };

    void foo() {
        LockedContext lck(this);
        lck.do_something();
    }
};
wandrien ★★
() автор топика
Последнее исправление: wandrien (всего исправлений: 3)
Ответ на: комментарий от wandrien

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

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

А в structural sharing ничего магического нет на самом деле.

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

Ты прекратишь решать «любую другую задачу, кроме той, что рассматривалась в ОП», или ты это делаешь специально?

Я всего лишь продемонстрировал что никакая статическая типизация не поможет Вам гарантированно взять правильный мьютекс даже в сильно упрощённом варианте.

bugfixer ★★★★★
()