Pull to refresh

Comments 32

Mono.Tasklets (позволяет такое проворачивать более цивилизованно, но только под Mono). А вообще это всё чёрная магия, в цепочке вызовов запросто может быть код, который к таким фортелям мягко говоря не готов.
Моя реализация работает на .Net любых версий, а потому не вижу, почему бы ей не быть также «цивилизованной» =) Цепочка вызовов к фортелям будет готова, т.к. дополнительно сохраняется и восстанавливается контекст потока, дополнительно исправляя ссылки на стек на регистрах. Т.е. запускающий код будет иметь корректный контекст на обоих потоках.
А если я где-то запомнил id потока с которым работаю, а после переключения он уже окажется другим? Мой код не сойдет с ума?
Ваш код должен учитывать что вы делаете Clone() Он же Ваш =)
Запомнить вполне могла какая-то используемая либа для контроля внутреннего состояния.
Цепочка вызовов к фортелям будет готова, т.к. дополнительно сохраняется и восстанавливается контекст потока, дополнительно исправляя ссылки на стек на регистрах.
Выше по стеку может быть код, работающий с неуправляемым ресурсом (или вообще фрейм с нативным кодом). Ресурс два раза освовобождается, пшшшш… Бабах!
Что-то вроде этого
var ptr = Marshal.AllocHGlobal(1024);
CallSomeNativeFunc(ptr);
CallYourCodeWithBlackMagic();
Marshal.FreeHGlobal(ptr); //Сюда мы попадаем два раза из разных потоков
Поэтому в самой библиотеке стоит ограничение до вызывающего метода. Это просто контролировать и логично само по себе. Бесконтрольный выход из вызывающего метода даст гору проблем с использованием.
Если не давать вылезти дальше вызывающего метода, то логичнее использовать замыкания, ибо они и так дают доступ ко всему, что доступно в этом самом методе в момент вызова.
Способ с Clone() дает те же преимущества. Просто, вы будете использовать другой стиль программирования.
Даже без выхода вы огребёте кучу проблем. Предположим, что где-то выше по коду у вас вызвался нативный метод, который вызвал снова код на C#/Java передав ему в качестве аргумента адрес переменной на стеке. Буфер какой-нибудь. Вы, «склонировав» поток теперь обращаетесь к этому буферу из двух потоков параллельно. Вряд ли это кончится добром.

Только не надо про то, что «так никто не делает»: вы же нечто подобное делаете. Вот в этой самой библиотеке, в частности.

То есть как средство обратной разработки или для отладки — это неплохое средство, но упаси вас бог использовать эту технологию в релизе. Это ж бомба замедленного действия просто!
опять же нет, т.к. я копирую контекст от вызывающего метода до вложенного вызова C++/CloneThread. Это значит что я оперирую только тем, что знаю, как работает. Да, есть ограничение что вызывающий метод не должен иметь ref и out параметров (создают ссылки на стек — это как раз то, о чем Вы говорите), однако если в вызывающем методе их нет, все нормально. Все что выше по стеку — не важно, т.к. он не копируется.
Ссылки на «всё что выше по стеку» могут быть засунуты куда угодно, вплоть до глобальных переменных если у вас есть нативный код. И поскольку вы это не копируете, оба потоку у вас могут работать с одними и теми же данными.
Вы меня не слышите. В переносимый в другой поток кусок стека входят только мои методы + 1 пользовательский, который не должен содержать out и ref параметры. про свои методы я все знаю: они таким недостатком не обладают. Про пользовательский: если условие соблюдено, он также не будет создавать таких ссылок.
А как быть с Thread Local Storage, он сохранится? Или мой код (опять же может быть код какой-то либы) проснется и увидит что все пропало?
А вообще, чисто академически, штука крутая ))
Нет, я спрашиваю о том что будет если переменную _sync пометить атрибутом [ThreadStatic]? Все перестанет работать, потому что вызов Fork.CloneThread накладывает на последующий код все ограничения, которые на нем бы были, если бы он явно запустился в другом потоке.
Но в отличие от явного запуска в другом потоке (через пул, TPL или просто Thread) в том, что ретурн вернет нас в код который ничего не знает о том что он в новом потоке, он обратится к своей [ThreadStatic] переменной, а она пустая и «все пропало».
аааа, ну это да. Но тут будет такое же поведение как и у метода, помещенного в пул потоков. он также если обратится к такой переменной получит «все пропало», так что все логично
Реализация не зависит от конечного языка, т.к. написана на C++/Asm, потому может быть подтянута и в Java. Просто я — .Net чик и не в курсе как протянуть pinvoke c Java до C++.
Мне кажется, JVM может, мягко говоря, с ума сойти от того, что вдруг под ней появился поток, который ей не создан. Странно что .Net такое позволяет. И там, и там для многопоточности есть свои стандартные инструменты, вполне удобные.
Поток создается .Net'ом. Для этого проброшен вызов из C++/Asm
А реальное применение в жизни всего этого какое?
Реальное применение… Ну такое же как и у ThreadPool.QueueForUserWorkItem( () => {… }); Просто стиль программирования другой получается
Не совсем понял, в случае ThreadPool это пул потоков. Tasks внутри выполняются по мере освобождения потоков. Т.е. потом забирает task из queue когда освобождается. Я так понял? Просто я java программист и не знаю всех тонкостей c#.

Ну и все же когда это нужно. В java можно сделать new Thread(new Runnable(){})).start() и будет у тебя порожден второй поток. Зачем такие сложности.
Для чего это делать
Самое важное, для чего это делается — для закрепления понимания, как все работает и что если знать, можно этим начать манипулировать.


(a) Just for fun (b) для понимания как работают потоки
Про пул потоков: в данном случае код склонированного flow выполнится во взятом из пула, потоке. По окончании исполнения метода поток будет обычным образом возвращен в пул
Извините, а GetThreadContext+SetThreadContext нельзя использовать?
А чем это может помочь? Вы установите таким образом контекст нового потока таким же как родителя, включая стек. И у вас программа попрет в обоих потоках по одному и тому же участку памяти (стеку), что приведет к непредсказуемым последствиям.
Никто не запрещает поменять, что нужно, в _CONTEXT. Просто меньше ручной работы.

Не говоря об этих флагах:
// CONTEXT_CONTROL specifies SegSs, Rsp, SegCs, Rip, and EFlags.
//
// CONTEXT_INTEGER specifies Rax, Rcx, Rdx, Rbx, Rbp, Rsi, Rdi, and R8-R15.
//
// CONTEXT_SEGMENTS specifies SegDs, SegEs, SegFs, and SegGs.
//
// CONTEXT_DEBUG_REGISTERS specifies Dr0-Dr3 and Dr6-Dr7.
//
// CONTEXT_MMX_REGISTERS specifies the floating point and extended registers
// Mm0/St0-Mm7/St7 and Xmm0-Xmm15).
//
Если идти путем контекста, тогда инитить дочерний поток будет родительский. В моем случае дочерний поток инитит сам себя. В общем, разницы почти нету =) Только если в производительности. И то вопрос… ) ИМХО, решения получатся одинаковыми.
Нет, получить и установить контекст можно только для остановленного потока, т.е. нужен третий ThreadCloneWorkerThread. Или надо запустить поток, в который будут переданы тиды двух потоков, которые будут остановлены, а после восстановлены с самовыпилом третьего потока.

Не спорю, решений можно придумать много. Просто интуитивно ожидаешь, что системные функции отлажены. Вот они, значения всех регистров, получены, меняй на здоровье. Установлены тоже будут системной функцией. Плюс, заголовки/функции есть и под x86, и под 64.
Этот стиль кодинга напоминает мне очень хорошо знакомый мне стиль «говнокодинга».

Основные особенности стиля:

-при форсмажоре разработчика — отпуск, увольнение (как правило, единственного) всё перестает работать и переписывается
-как правило, код выглядит простым, но совершенно невозможным (невероятно сложным) при удалённой отладке
-после переписывания стандартыми средствами («чистый» C#, без межплатформенных вызовов и т/д), код становится более
1) портируемым
2) читаемым
3) отлаживаемым

и, самое главное — как правило — более быстрым.
Sign up to leave a comment.