Минутка WTF-15: never mind
От: Sinix  
Дата: 18.01.17 05:59
Оценка: 5 (1)
Сегодня простенькое, в прошлый раз
Автор: Sinix
Дата: 08.01.17
простенькое…
Народ, давайте тож участвуйте. Веселее будет.

Назад к делу
        public static void Main(string[] args)
        {
            bool isComplete = false;

            int i = 0;
            var t = new Thread(() =>
            {
                while (!isComplete)
                    i++;
            });

            t.Start();

            Thread.Sleep(500);
            isComplete = true;
            t.Join();
            Console.WriteLine("complete! loops:" + i);
            Console.ReadKey();
        }


Что не так-то?


Спонсор вопроса снова stackoverflow, но посылку я вам не отдам ссылка после ответа будет, иначе неинтересно.
Минутка WTF
Re: Минутка WTF-15: never mind
От: vasmann  
Дата: 18.01.17 06:18
Оценка: 30 (2) +2
Здравствуйте, Sinix, Вы писали:

S>Сегодня простенькое, в прошлый раз
Автор: Sinix
Дата: 08.01.17
простенькое…

S>Народ, давайте тож участвуйте. Веселее будет.


S>Что не так-то?


Замыкание, как следствие анонимный тип с полем isComplet не валатильным.
Re[2]: Минутка WTF-15: never mind
От: vasmann  
Дата: 18.01.17 06:42
Оценка: 5 (1)
Здравствуйте, vasmann, Вы писали:

V>Замыкание, как следствие анонимный тип с полем isComplet не валатильным.


Ну и как следствие (вроде очевидное, но решил написать): есть вероятность, что значение закешируется и изменения переменной isComplete из основного потока никак не повлияет на дочерний, таким образом дочерний будет крутиться вечно.
Re[3]: Минутка WTF-15: never mind
От: vasmann  
Дата: 18.01.17 07:02
Оценка: 80 (2)
Здравствуйте, vasmann, Вы писали:

V>таким образом дочерний будет крутиться вечно.


Ну если уж совсем быть точным, то зависит от настройки сборки, если вместе /checked собрана, то не вечно — упадет с OverflowException-ом.
Я закончил
Re[4]: Минутка WTF-15: never mind
От: Sinix  
Дата: 18.01.17 07:49
Оценка:
Здравствуйте, vasmann, Вы писали:

V>Ну если уж совсем быть точным, то зависит от настройки сборки, если вместе /checked собрана, то не вечно — упадет с OverflowException-ом.

V>Я закончил


Ок, добиваем.

Во-первых, коварность этой ошибки в том, что она не воспроизводится ни на отладочных сборках, ни в релизных с подключённым отладчиком (пока в настройках не снята галочка "Suppress JIT optimization on module load"), ни в отдельных environments (пруф с mono).

UPD, с подачи ув. Sharov: всё ещё забавней. Конкретно этот код отрабатывает нормально с x86 JIT. А вот вариант с SO — нет.

Мораль: интеграционные тесты таки стоит запускать и в отладочной, и в релизной версиях. И на тех рантаймах, где код будет работать.


Во-вторых, ссылки.
Собственно вопрос.
Короткое объяснение матчасти (и таки да, явный Thread.MemoryBarrier(); внутри while работает. Внезапно.)
Оно же, но подробнее

Ну и наконец, разбор собственно вопроса и ответ от roslyn team.
Отредактировано 19.01.2017 11:51 Sinix . Предыдущая версия .
Re[5]: Минутка WTF-15: never mind
От: fddima  
Дата: 18.01.17 16:56
Оценка: +1
Здравствуйте, Sinix, Вы писали:

S>Собственно вопрос.

Обожаю лучшие ответы на SO:

Of course, you cannot add it, because it's a local variable. And of course, since it is a local variable, it should not be needed at all, because locals are kept on stack and they are naturally always "fresh".

С какой это радости стэк вдруг стал чем-то особенным? Стэк ведь обычный кусок памяти со всеми вытекающими. В некоторых языках передают ссылки/указатели на стэк, как часть нормального процесса. Даже в шарпе можно это сделать.
Re[6]: Минутка WTF-15: never mind
От: Sinix  
Дата: 18.01.17 17:00
Оценка:
Здравствуйте, fddima, Вы писали:

F> С какой это радости стэк вдруг стал чем-то особенным? Стэк ведь обычный кусок памяти со всеми вытекающими. В некоторых языках передают ссылки/указатели на стэк, как часть нормального процесса. Даже в шарпе можно это сделать.


Да-да-да, и не в преферанс, а в спортлото и далее по тексту. Вот это полезней будет.

Ну и про "в шарпе можно это сделать" — спасиб, напомнили про очередную тему для минутки хардкора. Ща всё будет тынц
Автор: Sinix
Дата: 18.01.17
.
Отредактировано 18.01.2017 17:24 Sinix . Предыдущая версия .
Re[7]: Минутка WTF-15: never mind
От: fddima  
Дата: 18.01.17 17:44
Оценка:
Здравствуйте, Sinix, Вы писали:

F>> С какой это радости стэк вдруг стал чем-то особенным? Стэк ведь обычный кусок памяти со всеми вытекающими. В некоторых языках передают ссылки/указатели на стэк, как часть нормального процесса. Даже в шарпе можно это сделать.

S>Да-да-да, и не в преферанс, а в спортлото и далее по тексту. Вот это полезней будет.
Сейчас немного лень разбираться, что конкретно они там в issue имеют ввиду. Понятно замыкания и всё такое, я не шибкий их фанат ещё с первых версий — простейший код легко превращается в ерунду (не помню реальный пример, но псевдопример такой):
void OnSomeEvent(int ev)
{
    // (1) замыкание создаётся тут.
    if (ev == 0) return;

    // (2) а должно по месту использования
    // И не факт что нужно создавать один огромный объект для всех последующих бранчей, если здесь будут ветвления.
    // Понятно, что на каждый вызов необходимо переиспользовать holder, но это нужно делать как оптимизацию,
    // а не как пессимизацию и создавать holder всегда со всем чем можно.
    // Таким незамысловатым образом в C++ с ручным bind - получается наилучший код. Позволю себе всем напомнить, что
    // неважно как быстро аллоцируются объекты - ещё быстрее это не аллоцировать их вообще, если не нужны.
    // (1) написан именно для этого, но сюрприз-сюрприз... в шарпе - альтернатива только разбить метод на два,
    // что на мой взгляд с точки зрения ежедневной работы с кодом - ещё хуже чем "холостая" аллокация.
    dispatcher.Queue(() => OnSomeEventInternal(ev));
}

Не проверял на последних компиляторах, но это я к тому — что понятно, что без них не жизнь, но основные кейсы — не требуют никаких volatile. И имхо, если код становится нетривиальным/нелинейным, да ещё и вокруг потоков — замыкания ни разу не друзья. Потом это гораздо тяжелее дебажить, а колстэки менее информативны (т.к. указывают не на уникальное имя метода, а на один "здоровый").

Но и там в коментах проскакивает то, что volatile нафиг не упал в текущих .NET, да и всепрощающий x86/amd64 тоже не способствует к "тру" коду... Не понимаю как вообще об этом всём можно думать в контексте .NET: рантайм творит не совсем то, что в спецификации. Имхо, всё ещё куча кода просто не сможет работать ни на слабой модели памяти, ни с более серьёзным оптимизатором/пессимизатором:

Старый добрый шаблон (1):
void OnMyEvent()
{
   if (MyEvent != null) MyEvent();
}


Все мы помним, что правильно это делать так (2):
void OnMyEvent()
{
   var handler = MyEvent;
   if (handler != null) handler();
}


Забудем про оператор "?." т.к. он здесь не может ничего добавить.

Так вот, компилятор волен "пессимизировать" здесь, и развернуть к коду как в варианте (1). Ровно как и (1) компилятор волен привести к (2). При этом никаких средств управлять этим нет. Насколько я знаю ни компилятор ни JIT таким не занимаются, но им никто не мешает это делать в будущем. Какие замыкания и volatile, если гораздо более фундаментальные и простые вопросы никак не решены / не специфицированы?

PS: Вполне вероятно я что-то из нового упустил и этот вопрос специфицирован.
Отредактировано 18.01.2017 17:45 Mystic Artifact . Предыдущая версия .
Re[5]: Минутка WTF-15: never mind
От: Sharov Россия  
Дата: 19.01.17 11:36
Оценка: 25 (1)
Здравствуйте, Sinix, Вы писали:

S>Здравствуйте, vasmann, Вы писали:


V>>Ну если уж совсем быть точным, то зависит от настройки сборки, если вместе /checked собрана, то не вечно — упадет с OverflowException-ом.

V>>Я закончил
S>

S>Ок, добиваем.


S>Во-первых, коварность этой ошибки в том, что она не воспроизводится ни на отладочных сборках, ни в релизных с подключённым отладчиком (пока в настройках не снята галочка "Suppress JIT optimization on module load"), ни в отдельных environments (пруф с mono).


Никак не удалось воспроизвести: x86, release, optimize code, галочка "Suppress JIT optimization on module load" снята. Полет нормальный. ЧЯДНТ?
Кодом людям нужно помогать!
Re[6]: Минутка WTF-15: never mind
От: Sinix  
Дата: 19.01.17 11:47
Оценка:
Здравствуйте, Sharov, Вы писали:

S>Никак не удалось воспроизвести: x86, release, optimize code, галочка "Suppress JIT optimization on module load" снята. Полет нормальный. ЧЯДНТ?


Всё так — конкретно этот пример под x86 отрабатывает нормально, только под x64. А вот если чуть-чуть поменять (вариант из SO)…
public static void Main(string[] args)
{            
   bool isComplete = false;

   var t = new Thread(() =>
   {
       int i = 0;

        while (!isComplete) i += 0;
   });

   t.Start();

   Thread.Sleep(500);
   isComplete = true;
   t.Join();
   Console.WriteLine("complete!");
}


Сюрприз, да. Ответ выше поправлю.
Re: Минутка WTF-15: never mind
От: Sharov Россия  
Дата: 19.01.17 11:49
Оценка:
Здравствуйте, Sinix, Вы писали:

S>Сегодня простенькое, в прошлый раз
Автор: Sinix
Дата: 08.01.17
простенькое…

S>Народ, давайте тож участвуйте. Веселее будет.

S>Назад к делу

S>
S>        public static void Main(string[] args)
S>        {
S>            bool isComplete = false;

S>            int i = 0;
S>            var t = new Thread(() =>
S>            {
S>                while (!isComplete)
S>                    i++;
S>            });

S>            t.Start();

S>            Thread.Sleep(500);
S>            isComplete = true;
S>            t.Join();
S>            Console.WriteLine("complete! loops:" + i);
S>            Console.ReadKey();
S>        }
S>


Мне казалось, что Join() и есть барьер, т.е. он обновит\синхронизирует переменные потока (звучит странно, да). Т.е. я думал тут возможен косяк в логике, где вроде ожидаем результат сразу после "isComplete = true;", а на деле только поcле Join.
Кодом людям нужно помогать!
Re[2]: Минутка WTF-15: never mind
От: Sinix  
Дата: 19.01.17 12:15
Оценка: 27 (2)
Здравствуйте, Sharov, Вы писали:

S>Мне казалось, что Join() и есть барьер, т.е. он обновит\синхронизирует переменные потока (звучит странно, да).

Тут проблема не в собственно в барьере, а в оптимизациях jit. Он выносит чтение поля из тела цикла.

Наличие в теле метода (il-метода, а не шарповского) некоторых инструкций (см The following implicitly generate full fences) вызывают такое поведение (упрощённо):

Therefore, language compilers, the CPU, and the JIT compiler will not reorder memory operations (read or write) that appear in your program before the call to MemoryBarrier so that they occur after the call to MemoryBarrier when the program is executed. Similarly, memory accesses that appear in your program after the call will not be executed before the call.

(с). Т.е. явный вызов Thread.MemoryBarrier() не позволяет кэшировать isComplete в локальной переменной.

По факту же, получить стабильно зависающий пример на x86/x64 довольно сложно. Даже простой вызов (раскомментировать CallMe):
        public static void Main(string[] args)
        {
            bool isComplete = false;

            var t = new Thread(() =>
            {
                int i = 0;

                while (!isComplete)
                {
                    i += 0;
                    //CallMe(i);
                }
            });

            t.Start();

            Thread.Sleep(500);
            isComplete = true;
            t.Join();
            Console.WriteLine("complete!");
        }
        private static void CallMe(int i)
        {
            i++;
        }

— и мы получаем "рабочий" код, с непредсказуемым шансом на дедлок. Документировано только то, что такой код _может_ не работать, но никак не обратное.
Re: Минутка WTF-15: never mind
От: namespace  
Дата: 22.01.17 09:42
Оценка:
S>Сегодня простенькое, в прошлый раз
Автор: Sinix
Дата: 08.01.17
простенькое…

S>Народ, давайте тож участвуйте. Веселее будет.
Это не простенькое, а примитивненькое. Скучно.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.