Thread введет себя странно
От: SanyaVB  
Дата: 12.01.17 10:36
Оценка:
Привет всем!

Удивительная ошибка возникла с работой потоками на ровном месте!

Thread thread = new Thread(delegate (object qq) { while(true) Console.WriteLine(qq); }) { IsBackground = true };
thread.Start(57);
thread.Join();


Вот такой код работает. Создается поток и бесконечно выводит на консоль 57.
А теперь привожу код, который не запускает поток... точнее thread.ThreadState указывает ан то что поток запустился, но ни в отладчике ни на консоль текст 57 не выводит:

Thread thread = new Thread(QQ) { IsBackground = true };
thread.Start(57);
thread.Join();

        private static void QQ(object qq)
        {
            while (true) Console.WriteLine(qq);
        }



Думал возникает исключение. обернул в TRY/CATCH — исключение не возникает. Состояние потока возвращает что он работает.

Еще приведу пример который не выполняет поток:

Thread thread = new Thread(delegate (object qq) { Console.WriteLine("Этот текст не отображается!!!");  QQ(qq); }) { IsBackground = true };
thread.Start(57);
thread.Join();

        private static void QQ(object qq)
        {
            while (true) Console.WriteLine(qq);
        }


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

Есть ли идеи с чем может быть связано??? Я вот думаю может что-то не так со стеком (в начале программы, а именно в Main таких глюков нет)??? как можно узнать максимальный размер стека и на сколько он заполнен?
Отредактировано 12.01.2017 15:42 AndrewVK . Предыдущая версия .
thread
Re: Thread введет себя странно
От: Sinix  
Дата: 12.01.17 10:50
Оценка: +1
Здравствуйте, SanyaVB, Вы писали:

SVB>А теперь привожу код, который не запускает поток... точнее thread.ThreadState указывает ан то что поток запустился, но ни в отладчике ни на консоль текст 57 не выводит:


Полный код давай. Отлично работает
        private static void QQ(object qq)
        {
            while (true)
                Console.WriteLine(qq);
        }
        static void Main(string[] args)
        {
            Thread thread = new Thread(QQ) { IsBackground = true };
            thread.Start(57);
            thread.Join();

            Console.WriteLine("!!!");
            Console.ReadKey();
        }


* скорее всего у тебя соседний поток вывод в консоль заблокировал. Фигню написал. input не блокирует вывод в консоль.
Отредактировано 12.01.2017 10:54 Sinix . Предыдущая версия .
Re[2]: Thread введет себя странно
От: SanyaVB  
Дата: 12.01.17 11:09
Оценка: 50 (1) +1
Здравствуйте, Sinix, Вы писали:

Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:
    class Program
    {
        static void Main(string[] args)
        {
            A a = A.Instance;
        }
    }

    class A
    {
        private readonly static A instance = new A();

        public static A Instance
        {
            get { return instance; }
        }

        private A()
        {
            Thread thread = new Thread(QQ) { IsBackground = true };
             thread.Start(57);
             thread.Join();

             Console.WriteLine("!!!");
             Console.ReadKey();
        }
        private void QQ(object qq)
        {
            while (true)
                Console.WriteLine(qq);
        }
    }
Re[3]: Thread введет себя странно
От: Слава  
Дата: 12.01.17 11:15
Оценка:
Здравствуйте, SanyaVB, Вы писали:

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


SVB>Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:


Что же, основной тред завершается раньше, после чего дополнительный дохнет, не стартовав?
Re[3]: Thread введет себя странно
От: Sinix  
Дата: 12.01.17 11:15
Оценка: 6 (1)
Здравствуйте, SanyaVB, Вы писали:

SVB>Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:


А, знакомая штука. Объяснение:
https://ericlippert.com/2013/01/31/the-no-lock-deadlock/

Правило большого пальца — никакой тяжёлой логики в static-конструкторах или в инициализаторах static-полей. Lazy<T> в помощь
Re[4]: Thread введет себя странно
От: SanyaVB  
Дата: 12.01.17 11:39
Оценка:
Здравствуйте, Sinix, Вы писали:

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


SVB>>Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:


S>А, знакомая штука. Объяснение:

S>https://ericlippert.com/2013/01/31/the-no-lock-deadlock/

S>Правило большого пальца — никакой тяжёлой логики в static-конструкторах или в инициализаторах static-полей. Lazy<T> в помощь

  class Program
    {
        static void Main(string[] args)
        {
            A aa = new A();
            Console.WriteLine("60");
        }
    }

    class A
    {
        public A()
        {
            Thread thread = new Thread(delegate(object qq) { Console.WriteLine(qq); }) { IsBackground = true };
            thread.Start(55);
            thread.Join();

             thread = new Thread(QQ) { IsBackground = true };
             thread.Start(57);
             thread.Join();
        }
        private void QQ(object qq)
        {
            Console.WriteLine(qq);
        }
    }

Тут нет статики, а тоже дохнет
Re[4]: Thread введет себя странно
От: SanyaVB  
Дата: 12.01.17 11:44
Оценка: :)
Здравствуйте, Слава, Вы писали:

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


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


SVB>>Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:


С>Что же, основной тред завершается раньше, после чего дополнительный дохнет, не стартовав?



Нет. Все два потока работают! Во всяком случае так показывает отладчик. Пахнет тут DeadLock -ом. Сначала подумал из-за статики, но и если статику убрать тоже самое. Это видимо как-то связано с формированием объекта. Пока не выйдем из конструктора — поток не станет выполняться, а в конструкторе у нас Join. в итоге мы из него не выйдем никогда, а он никогда не начнет выполняться, хоть и будет находиться в статусе выполнения.

Но вот почему???? Это Глюк .NET или ...???
Re[5]: Thread введет себя странно
От: Sinix  
Дата: 12.01.17 12:06
Оценка: +1
Здравствуйте, SanyaVB, Вы писали:

SVB>Тут нет статики, а тоже дохнет

Да ну?
http://volatileread.com/utilitylibrary/snippetcompiler?id=101390

(кнопочку run нажать).
Re[6]: Thread введет себя странно
От: SanyaVB  
Дата: 12.01.17 12:29
Оценка:
Здравствуйте, Sinix, Вы писали:

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


SVB>>Тут нет статики, а тоже дохнет

S>Да ну?
S>http://volatileread.com/utilitylibrary/snippetcompiler?id=101390

S>(кнопочку run нажать).

Косякнул
Re[4]: Thread введет себя странно
От: SanyaVB  
Дата: 12.01.17 13:07
Оценка:
Здравствуйте, Sinix, Вы писали:

Вот такое решение придумал
Re[5]: Thread введет себя странно
От: Sinix  
Дата: 12.01.17 13:23
Оценка: +3
Здравствуйте, SanyaVB, Вы писали:

SVB>Вот такое решение придумал

SVB>

Не надо так.
        public static A Instance
        {
            get
            {
                if (Interlocked.CompareExchange(ref flag, 1, 0) == 0) // (1)
                    instance.Initialize();
                return instance;                                      // (2)
            }
        }

два потока доходят до строки (1), первый засыпает.
Второй продолжает до строки "instance.Initialize();" и засыпает
Первый просыпается и доходит до строки (2). Упс.

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


Если цензурно:

При попытке написать lock-free код есть всего два варианта: или изучить матчасть, или использовать готовые примитивы.
Для данного сценария — Lazy<T>. Рекомендую
Re[3]: Thread введет себя странно
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 12.01.17 19:15
Оценка: 151 (9) +1
Здравствуйте, SanyaVB, Вы писали:

SVB>Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:


А ты еще попробуй запустить свой код без отладчика и в релизе (по Ctrl + F5). Вот тогда удивишься!

А потом добавь явный статический конструктор в класс А и попробуй снова. Вот тогда удивишься еще сильнее

Как сказал ув. Sinix просто перепиши синглтон с field-like на lazy:

private readonly static Lazy<A> instance = new Lazy<A>(() => new A());
public static A Instance => instance.Value;


И все станет нормально.

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

1. Вызывается метод Main (это важно)
2. Вызывается статический конструктор класса А.
3. CLR проверяет, что статический конструктор еще не был вызван. Захватыват блокировку и вызывает статический конструктор класс А.
4. Статический конструктор А вызывает экземплярный конструктор А. Тот создает поток, запускает его и ожидает завершения. Все это происходит под блокировкой CLR.
5. CLR запускает новый поток, который пытается вызвать метод QQ. CLR снова проверяет, а был ли вызван статический конструктор класса А, ведь мы пытаемся обратиться к одному из методов этого класса. Видит, что он еще не был вызван и пробует вызвать его. Но до этого, этот поток также пробует захватить блокировку.
6. Результат: основной поток выполняет конструктор класса А под блокировкой CLR и ожидает завершение работы потока `thread`. А в это время вновь созданный поток сам залип на вызове метода QQ, ведь он тоже пытается получить блокировку CLR для вызова статического конструктора.

Теперь более интересный момент. Почему это дело работает при запуске в релизе и без отладчика.

А работает оно вот почему: поскольку в классе А нет явного статического конструктора, то CLR имеет право вызвать статический конструктор не в момент первого обращения к типу, а раньше, например, до вызова метода Main. Проверить это легко путем модификации метода Main следующим образом:

static void Main(string[] args)
{
    Console.WriteLine("Main");
    A a = A.Instance;
}


При запуске под отладчиком или в дебаге, будет видно, что вначале вызывается 'Main', а потом статический конструктор. А при вызове в релизе и без отладчика статический конструктор вызывается до вызова Main.
Теперь, когда статический конструктор вызывается до Main, CLR решает, что нет никакого смысла вызывать статический конструктор из под блокировки (хотя я не уверен, что в этом случае гонки действительно невозможны) и дедлок уже не происходит.

Я вот тут совсем недавно писал про разные реализации сингтонов. Может пригодиться:

Реализация синглтонов в .NET: Field-like vs. Lazy
О синглтонах и статических конструкторах
Re[4]: Thread введет себя странно
От: Sinix  
Дата: 12.01.17 21:35
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Реализация синглтонов в .NET: Field-like vs. Lazy

, как обычно.

Кстати, вопрос такой, а что вот тут имелось в виду?

(*) Название ‘field-like’ уходит корнями в существующую терминологию, как например, field-like events. Да, с C# 6, field-like реализация теперь содержит свойство, но классическая реализация была основана именно на закрытом поле.



Реализация event-ов в c#6 не менялась, пруф (см выхлоп в il).

Ну и если честно, я никогда не слышал, чтоб кто-то называл статические поля "field-like". Обычно "stored as a static field" говорят и всем уже всё понятно, readonly сам собой подразумевается. Мутабельные static-поля — это как шампанское по утрам. Редко кто так делает
Re[3]: Thread введет себя странно
От: alexzzzz  
Дата: 13.01.17 14:29
Оценка: 50 (1)
Здравствуйте, SanyaVB, Вы писали:

SVB>Да, кажись я нашел виновника. Интересный случай даже))) Можно на собеседовании спрашивать. Высылаю код в студию:

SVB>
SVB>    class Program
SVB>    {
SVB>        static void Main(string[] args)
SVB>        {
SVB>            A a = A.Instance;
SVB>        }
SVB>    }

SVB>    class A
SVB>    {
SVB>        private readonly static A instance = new A();

SVB>        public static A Instance
SVB>        {
SVB>            get { return instance; }
SVB>        }

SVB>        private A()
SVB>        {
SVB>            Thread thread = new Thread(QQ) { IsBackground = true };
SVB>             thread.Start(57);
SVB>             thread.Join();

SVB>             Console.WriteLine("!!!");
SVB>             Console.ReadKey();
SVB>        }
SVB>        private void QQ(object qq)
SVB>        {
SVB>            while (true)
SVB>                Console.WriteLine(qq);
SVB>        }
SVB>    }
SVB>


Под 3.5 виснет всегда, под 4+ виснет в дебаге, в релизе работает. Раз есть разница между версиями CLR, наверное, проблема связана с порядком инициализации статических полей при наличии/отсутствии статического конструктора.

Виснет, я так понимаю, из-за того, что запуск QQ в другом потоке ожидает окончания инициализация класса; а инициализация класса (статического поля у нас) не завершится, пока QQ таки не будет запущен. Дедлок.

А не виснет в релизе 4+, потому что статического конструктора нет и рантайм откладывает инициализацию статического поля до первого к нему обращения. А первое к нему обращение (запись в поле instance) происходит уже после старта QQ. Поэтому QQ запускается свободно, ничего не блокируя.

Правильно?

PS
Упс. Пока я день думал , SergeyT уже расписал.

PPS
Ещё раз упс.

В релизе 4+ не виснет, потому что инициализация статического поля происходит не перед первым обращением в полю, а «when the first method that refers to the class is jitted» — перед первым обращением к методу, который обращается к классу, который надо инициализировать.

PPPS
Надо заодно и под Mono посмотреть:

Релизная версия под CLR2 в Mono 2.6 (который используется в Unity) виснет, как в Net Framework.
Релизная версия под CLR4 в Mono 4.6.2 виснет тоже, в отличие от Net Framework.
Отредактировано 13.01.2017 17:54 alexzzzz . Предыдущая версия . Еще …
Отредактировано 13.01.2017 15:47 alexzzzz . Предыдущая версия .
Отредактировано 13.01.2017 14:38 alexzzzz . Предыдущая версия .
Отредактировано 13.01.2017 14:37 alexzzzz . Предыдущая версия .
Re[5]: Thread введет себя странно
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 13.01.17 23:10
Оценка:
Здравствуйте, Sinix, Вы писали:

ST>>Реализация синглтонов в .NET: Field-like vs. Lazy

S>, как обычно.

Спасибо!

S>Кстати, вопрос такой, а что вот тут имелось в виду?

S>

S>(*) Название ‘field-like’ уходит корнями в существующую терминологию, как например, field-like events. Да, с C# 6, field-like реализация теперь содержит свойство, но классическая реализация была основана именно на закрытом поле.

S>

Ну, я посчитал, что такое разделение в плане терминов вполне уместно, хотя и не совсем уверен, что это так Я хочу придумать термин, которым можно будет легко разделять все эти виды синглтонов и подумал, что field-like будет неплохим термином. Хотя может быть что-то типа 'field-based'? 'field initializer-based'? 'static constructor based'? или что-то такое.

S>Реализация event-ов в c#6 не менялась, пруф (см выхлоп в il).


S>Ну и если честно, я никогда не слышал, чтоб кто-то называл статические поля "field-like". Обычно "stored as a static field" говорят и всем уже всё понятно, readonly сам собой подразумевается. Мутабельные static-поля — это как шампанское по утрам. Редко кто так делает


Согласен.
Re[4]: Thread введет себя странно
От: SergeyT. США http://sergeyteplyakov.blogspot.com/
Дата: 13.01.17 23:23
Оценка: +1
Здравствуйте, alexzzzz, Вы писали:


A>PS

A>Упс. Пока я день думал , SergeyT уже расписал.

Сори

A>В релизе 4+ не виснет, потому что инициализация статического поля происходит не перед первым обращением в полю, а «when the first method that refers to the class is jitted» — перед первым обращением к методу, который обращается к классу, который надо инициализировать.


У меня в 4+ виснет на ура. Но зависит от того, есть ли статический конструктор явный или нет. Ну и проверять нужно в релизе и без отладки (т.е. по Ctrl + F5, а не по F5).

Ну и порядок вызова статического конструктора не просто происходит или не происходит перед первым обращением и связано это не с версией фреймворка, а с расслабленной или строгой семантикой. Вот кусок из поста Скита (точнее из ECMA 335):

A type may have a type-initializer method, or not.
A type may be specified as having a relaxed semantic for its type-initializer method (for convenience below, we call this relaxed semantic BeforeFieldInit)
If marked BeforeFieldInit then the type's initializer method is executed at, or sometime before, first access to any static field defined for that type
If not marked BeforeFieldInit then that type's initializer method is executed at (i.e., is triggered by):
first access to any static or instance field of that type, or
first invocation of any static, instance or virtual method of that type


Ну и получается, что при отсутствии явного статического конструктора, тип помечается BeforeFieldInit атрибутом и вызов статического конструктора может происходить *до* первого обращения к типу, например, перед вызовом метода, в котором этот тип используется.
Re[6]: Thread введет себя странно
От: Sinix  
Дата: 14.01.17 07:43
Оценка:
Здравствуйте, SergeyT., Вы писали:

ST>Я хочу придумать термин, которым можно будет легко разделять все эти виды синглтонов и подумал, что field-like будет неплохим термином.


Да это понятно. Вечная проблема с натянуть паттерны на реальный код: сказать "как оно есть на самом деле" обычно проще static field singleton в копилку идей

Но я про другое хотел спросить, про "например, field-like events. Да, с C# 6, field-like реализация теперь содержит свойство". Это как?
Там же нет свойства. Пара add-remove с первого шарпа генерилась, всё что поменялось — в четвёртом (по памяти, могу врать) шарпе подписку lock-free сделали.
Re[6]: Thread введет себя странно
От: Lexey Россия  
Дата: 16.01.17 10:33
Оценка: 50 (1)
Здравствуйте, Sinix, Вы писали:

S>Не надо так.

S>
S>        public static A Instance
S>        {
S>            get
S>            {
S>                if (Interlocked.CompareExchange(ref flag, 1, 0) == 0) // (1)
S>                    instance.Initialize();
S>                return instance;                                      // (2)
S>            }
S>        }
S>

S>два потока доходят до строки (1), первый засыпает.
S>Второй продолжает до строки "instance.Initialize();" и засыпает
S>Первый просыпается и доходит до строки (2). Упс.

Что-то ты перемудрил с засыпаниями. Тут и без них все прекрасно ломается. Первый поток уходит в инициализатор, второй получает недоинициализированный объект. Кстати, в предыдущем релизе Linq2Db такой кусоккосяк тоже имелся. В текущем уже пофиксили.
"Будь достоин победы" (c) 8th Wizard's rule.
Отредактировано 16.01.2017 10:33 Lexey . Предыдущая версия .
Re[7]: Thread введет себя странно
От: Sinix  
Дата: 16.01.17 11:07
Оценка:
Здравствуйте, Lexey, Вы писали:

L>Что-то ты перемудрил с засыпаниями. Тут и без них все прекрасно ломается.

Да это понятно. С моделью "в один момент времени выполняется только один поток" проще объяснять. У народа мозг не взрывается


L>Кстати, в предыдущем релизе Linq2Db такой кусоккосяк тоже имелся. В текущем уже пофиксили.


Ещё один подарок SergeyT в ErrorProne.Net.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.