Здравствуйте, ·, Вы писали:
_>>Ну может стоило приводить цитату целиком, а не обрезать на самом интересном? ))) Целиком оно выглядит так: ·>
_>>A data race has Undefined Behavior, and is therefore impossible to perform in Safe Rust. Data races are mostly prevented through Rust's ownership system: it's impossible to alias a mutable reference, so it's impossible to perform a data race. Interior mutability makes this more complicated, which is largely why we have the Send and Sync traits (see below).
_>>Так что там про гарантии? ))) ·>Ой. Перечитал повнимательнее. Гарантии в первом предложении. Impossible и точка. Смысл mostly тут совсем в другом. ·>Гонки данных в основном предовращены благодаря системе владения. Однако, этого не хватило, поэтому пришлось ещё ввести traits Send и Sync. ·>Т.е. гарантия отсутсвтия data race в Safe Rust обеспечивается совокупностью BC, Send и Sync.
Ты правильно понял про mostly, но не правильно понял роль Send/Sync — она не усиливающая, а скорее ослабляющая. Т.е. если бы был полный запрет на уровне BC, то это давало бы больше гарантий, но получился бы непригодный для использования язык (точнее все бы просто сидели там в одном гигантском unsafe). А Send/Sync позволяет сказать компилятору: "поверь мне на слово, к этому классу можно спокойно обращаться из многих потоков". Так что "Impossible" — это такая себе шутка... )))
Здравствуйте, alex_public, Вы писали:
_>Ты правильно понял про mostly, но не правильно понял роль Send/Sync — она не усиливающая, а скорее ослабляющая. Т.е. если бы был полный запрет на уровне BC, то это давало бы больше гарантий, но получился бы непригодный для использования язык (точнее все бы просто сидели там в одном гигантском unsafe). А Send/Sync позволяет сказать компилятору: "поверь мне на слово, к этому классу можно спокойно обращаться из многих потоков". Так что "Impossible" — это такая себе шутка... )))
А ничего что компилятор их сам реализует везде где можно?
А их ручная реализация unsafe. Соответственно если ты их реализуешь сам, то ты берёшь всю ответственность на себя.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, Иван Дубров, Вы писали:
_>>Да, он ругается на доступ к ней из нескольких потоков без unsafe, но тут то у нас будет один поток, так что никаких претензий. ИД>Даже в одном потоке доступ может быть UB, если берётся несколько ссылок на один static mut.
моём примере.
_>>Единственно что я не в курсе есть ли сейчас в Rust'е библиотека сопрограмм (типа Boost.Coroutine2), чтобы можно было попробовать записать такой код. В самом языке сопрограмм почему-то (странно для вроде как современного языка — уже даже в древнем C++ их сейчас вводят на уровне языка, а не библиотеки) нет. ИД>Сопрограммы -- stackless или stackful? Как я понимаю, stackful были в Rust в виде green threads, но их убрали из-за несоответствия философии Rust "zero-cost abstractions". Stackless coroutines могут быть построены на futures/async/await, но эта область сейчас только-только стандартизуется.
Stackful всегда могут быть подключены как библиотека (как раз такой и является Boost.Coroutine2). А на некоторых платформах вообще даже есть в OS API (например на винде под именем fibers), но там уже не так удобно использовать.
А вот stackless ты никак не сделаешь без поддержки языка. Ну точнее конечно же можно ручками сделать конечный автомат из асинхронных колбеков и оно даже заработает, но полностью убьёт саму цель подключения сопрограмм — линеаризацию асинхронного кода.
ИД>Впрочем, использовать futures можно уже довольно давно -- насколько я понял, dropbox что-то подобное делают в своём движке синхронизации -- они используют future как сопрограммы. Может быть даже ранний async/await используют.
Ну так использование "продолжений" — это ещё один вариант организации асинхронного кода, который выглядит конечно же покрасивее голой лапши колбеков, но даже близко не дотягивает до линеаризованного сопрограммами кода.
Здравствуйте, alex_public, Вы писали:
_>>>·>sun.misc.Unsafe, jdk.internal.misc.Unsafe. _>>>Ну если оно есть в Java, то ты конечно же сможешь показать мне описание этого API в официальной документации Java, да? ))) _>·>Причём тут официальная документация? _>Потому что если какой-то API предлагается к использованию, то он обязательно подробно описывается авторами в официальной документации. Не так ли? )
Ок... И что? Какое это отношение имеет к "Полностью автоматическое и не требующее мозга для использование"? Взял скопипастил кусок кода с SO, и никакой оф-доки не надо.
_>·>Так это следствия из твоих утверждений, вот и бред. _>Вообще то я как раз утверждал полностью обратное. Я говорил что разработчику на Java (так же как и на Питоне и на других языках со сборщиком мусора) не надо знать низкоуровневых приёмов управления памятью. А ты как раз начал это опровергать, обосновывая бредом про FFI.
Нет, ты сказал "Полностью автоматическое и не требующее мозга для использование". Про низкоуровневость речи не было, ну допустим, отложим факты про native/unsafe т.к. они тебе не нравятся и ты хочешь их проигнорировать.
Но ты ещё проигнорировал и другие факты. в Java можно легко нарваться на OOM и иногда на ситуацию когда gc не справляется.
_>>>·>Зато они позволяют на ровном месте обратиться к освобождённой памяти. _>>>Только при их неверном использование. _>·>Сформулируй "неверное использование". _>Ссылки в C++ — это идеальный инструмент для передачи данных при вызове функций (т.е. вниз по стеку). В этой роли они эффективны и безопасны. Применение их для других целей практически всегда (на самом деле я не припомню вообще ни одного контрпримера, но на всякий случай оговариваюсь — мало ли какие сценарии я сходу не могу вспомнить) является не верным.
А если этот самый стек окажется в другом треде через магию [&], ничего? В твоём коде c mutex так и вышло. Стоит немного подрефакторить код, чтобы t.join() оказался немного в другом месте и всё нахрен улетит. Твой супер-пупер стат-ан такое найдёт?
_>>>И я естественно говорю не о внимательности программиста, проверяющего руками каждое использование, а об общем концептуальном подходе (который кстати можно и как правило статического анализатора задать) _>·>Нельзя задать. _>Я смотрю ты большой знаток статических анализаторов. )))
Покажи такой стат-ан, который будет _гарантировать_ следование концептуальному подходу.
_>·>Это опять какой-то бред. Концептуальный подход — это всё бла-бла. Вот тебе моя гениальная концепция: "Пишите программы без багов". Вот я изобрёл абсолютно безопасный язык программирования. Осталось сделать официальную документацию и готово, всем твоим критериям гарантий безопасности удовлетворяет. Нобелевку мне, можно две. _>Непонятно к чему весь этот глупый сарказм, если вся индустрия сейчас именно так и работает.
Раз ты так работаешь, это ещё не повод обобщать на индустрию.
_>>>Например в современном C++ коде должно быть практические исключено (за редкими исключениями, типа использования unsafe в Rust) использование явных new и delete. _>·>И что? Это избавит, скажем, от утечек, вызваных циклическими зависимостями? Или тупо от обращений к невалидной памяти? _>Естественно. Ну если конечно не рассматривать взаимодействие с какими-нибудь библиотеками на C и т.п., что не поддерживает безопасные инструменты C++.
Какие-то двойные стандарты. В С++ ты не хочешь рассматривать unsafe, а в Rust хочешь.
_>>>Вообще то в моей аналогии речь как раз про автоматизм и была. ))) Как в управление памятью, так и во многопоточности. И ты попытался утверждать, что Rust умеет автоматическое управление в области многопоточности. А когда я тебе показал что не имеет, ты говоришь "Ну и не говори об автоматизме. Кто ж тебя заставляет?"? _>·>Нет, я не пытался утверждать такое. Или цитату в студию. _>В ответ на мою цитату "В управление многопоточностью подобного решения пока к сожалению не существует!" (под подобным там выше явно указывалось автоматическое) ты написал длинный хвалебный текст про мьютексы в Rust'е — как это ещё можно было воспринимать? Т.е. если ты не спорил этим моим утверждением, то к чему был весь этот текст, просто ради того что бы хоть куда-то его написать? )))
Т.е. ты вначале заявил глупость, потом соврал. Ок, спасибо что хоть сознался. Я с твоим утверждением не спорил, а напомнил обсуждаемый тезис. А как ещё можно было среагировать straw man fallacy?
_>>>Так вот, даже реализация Atomic из стандартной библиотеки даёт доступ не только к простейшим гарантированным операциям типа fetch_add, но и к базовой функциональности CAS, которая при неаккуратном использование способна привести к потере данных. Ну а если этого недостаточно, то мы легко можно реализовать свой (более продвинутый чем-то) Atomic, который компилятор будет спокойно воспринимать и в котором при этом будут ещё более уязвимые места. _>·>Кул стори, бро. Тебе осталось понять и рассказать какое это всё отношение имеет к data race. _>Ну очевидно же. Язык позволяет сказать компилятору: просто поверь мне, что этот тип данных безопасен для одновременного обращения из разных потоков. Так что даже если мы там нарисуем ничем не синхронизированный доступ к общей памяти, компилятор спокойно это проглотит. Понятно, что никто специально такое делать не будет, но вот допустить ошибки при реализации какого-нибудь хитрого контейнера можно без проблем...
А так же можно допустить ошибки в ffi или и в компиляторе. И что? Проверим компилятор статическим анализатором? А ошибки в статическом анализаторе чем проверять будем?
_>>>На самом деле ничего страшного, конечно же при условии что ты не упёртый фанатик, верящий в миф типа "если компилятор Rust одобрит код без unsafe, то значит в нём точно всё в порядке с многопоточностю". _>·>Пока этот бред я слышал только от тебя. Ты упёртый фанатик? _>Ну если по итогу дискуссии в данной темке не останется людей, считающих что Rust решает проблемы многопоточности, то я буду считать свою миссию успешной. )
Он решает _проблему_ многопоточности "data race".
_>>>·>Каким образом эти гарантии будут обеспечиваться? _>>>В большинстве языков административно. В редких случаях (типа Эрланга) это может быть упаковано на уровне рантайма языка, но у этого всегда есть соответствующая цена. _>·>Верно. В большинстве языков — административно, а в rust — они обеспечиваются компилятором. В этом и отличие. _>Нет, в Rust гарантии корректной многопоточности ни не обеспечиваются, о чём честно написано в их документации.
В документации конкретно написано какие гарантии обеспечиваются.
_>·>Работающий код, делающий ровно то же что и твой. Доволен? _>Ну понятно, пошла клоунада — верный признак слива.
Какой вопрос, такой ответ.
_>>>>>Ну ты же понимаешь, что это означает как минимум "Rust не гарантирует отсутствие утечек памяти и других ресурсов, управляемых через RAII"? _>>>·>Ну да. И? _>>>Просто получается что в данном моменте "как бы гарантированный" Rust на практике имеет меньше гарантий, чем большинство других языков с RAII (включая тот же самый C++). _>·>Каким образом C++ обеспечивает гарантии отсутствия утечек памяти? _>C++ обеспечивает гарантии работы RAII (гарантии вызова деструктора). Следствием этого может быть и гарантии отсутствия утечек памяти, если применять только современные подходы к её управлению.
_>·>Это всё идеал. А конкретика? Что конкретно дают акторы? "точно всё в порядке с многопоточностю"? _>Для конкретики задавай конкретный вопросы с конкретными примерами. Писать обзорную статью о преимуществах модели акторов я тут не планировал. )))
Понятно, товарищъ стратёг.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Ну вот я наконец добрался до твоего фееричного сообщения. И что мне в нём особенно нравится, даже не надо придумывать "основное слово" для описания происходящего — ты его уже сам озвучил.
_>>
_>>Так вот, надеюсь не надо пояснять, почему результат работы этого теперь уже некорректного кода будет зависеть от случайных факторов? При этом вся работа с проблемной переменной будет происходить исключительно в рамках одного потока... WH>Вот это залёт так залёт. await блокирует исполнение до того момента как process_asyc вернёт результат.
Вот это просто феерично! Я честно не ожидал от тебя такого. Человек, который вроде как разрабатывал язык на базе C#, только лучше, оказывается не представляет как работают сопрограммы! Хотя через них работают важные функции языка (там и yield и async/await). Как ты вообще мог пользоваться этими инструментами с таким пониманием? Как чёрной магией что ли?)))
Да, и вообще, даже если не обладать знаниями, то просто банальный здравый смысл должен был подсказать даже самому слабому программисту, что какой тогда смысл в таком переходе на асинхронность, если она всё равно блокирует исполнение потока? А тебе даже такая очевидная мысль не закралась в голову...
Даже не знаю что тут можно сказать — данный "залёт" уже где-то за пределами моей иронии...
_>>Правильно. Только вот ассемблерная инструкция перемещения регистра в память (что равносильно оператору равенства в твоём языке программирования) является вполне себе атомарной, так что для int'ов(подразумевая под ним машинное слово) оно и так автоматически имеется. WH>Ещё один залёт. Не на всех платформах такое работает.
Ну раз ты это так смело утверждаешь, то для тебя конечно же не будет проблемой назвать хоть одну платформу, на которой ассемблерная инструкция записи значения из регистра в ячейку памяти не является атомарной.
_>>•>Никак не соотносится. Atomic не создают data race и не обладают undefined behaviour. _>>Да, но это же случай одновременного доступа на запись к некой общей памяти и по твоей цитате выше оно обязательно должно быть undefined behaviour в C++! WH>Опять залёт. Атомики производят синхронизацию. Так что тут нет никакого одновременного доступа.
С точки зрения ПО как раз никакой синхронизации там нет — соответствующие атомарные инструкции обращения к одной ячейке памяти без проблем запускаются одновременно на нескольких ядрах процессора. А то как разрешается этот вопрос внутри процессора (например у Intel для этого блокируется шина) — это его личное дело и может меняться в разных реализациях.
_>>>A data race has Undefined Behavior, and is therefore impossible to perform in Safe Rust. Data races are mostly prevented through Rust's ownership system: it's impossible to alias a mutable reference, so it's impossible to perform a data race. Interior mutability makes this more complicated, which is largely why we have the Send and Sync traits (see below).
_>>>Так что там про гарантии? ))) _>·>Ой. Перечитал повнимательнее. Гарантии в первом предложении. Impossible и точка. Смысл mostly тут совсем в другом. _>·>Гонки данных в основном предовращены благодаря системе владения. Однако, этого не хватило, поэтому пришлось ещё ввести traits Send и Sync. _>·>Т.е. гарантия отсутсвтия data race в Safe Rust обеспечивается совокупностью BC, Send и Sync. _>Ты правильно понял про mostly, но не правильно понял роль Send/Sync — она не усиливающая, а скорее ослабляющая. Т.е. если бы был полный запрет на уровне BC, то это давало бы больше гарантий, но получился бы непригодный для использования язык (точнее все бы просто сидели там в одном гигантском unsafe). А Send/Sync позволяет сказать компилятору: "поверь мне на слово, к этому классу можно спокойно обращаться из многих потоков". Так что "Impossible" — это такая себе шутка... )))
Ты похоже не способен воспринимать текст длиннее одного предложения. Теперь ты забыл слова "Safe Rust". Воткнуть Send/Sync как попало куда попало можно только с unsafe.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, alex_public, Вы писали:
_>А вот stackless ты никак не сделаешь без поддержки языка. Ну точнее конечно же можно ручками сделать конечный автомат из асинхронных колбеков и оно даже заработает, но полностью убьёт саму цель подключения сопрограмм — линеаризацию асинхронного кода.
_>Ну так использование "продолжений" — это ещё один вариант организации асинхронного кода, который выглядит конечно же покрасивее голой лапши колбеков, но даже близко не дотягивает до линеаризованного сопрограммами кода.
Ну да, в Rust для этой трансформации async/await есть (скоро будет и в стабильной версии).
Здравствуйте, alex_public, Вы писали:
_>Значит по тезисам: _>1. В данном конкретном примере присвоение global_var как раз будет атомарным. Потому как это int и C++ следит за выравниванием. Как это будет происходить на низком уровне скажем в процессорах Intel можешь глянуть например здесь https://software.intel.com/en-us/articles/software-techniques-for-shared-cache-multi-core-systems в пункте Avoid false sharing.
Ты мне пункт Стандарта покажи.
_>4. Насколько я вижу, вся эта дискуссия с тобой свелась к спору о терминологии, что весьма глупо. Лично мне абсолютно всё равно как называть конкретные явления, лишь бы договорить об одном языке для всех. Я могут сформулировать свою мысль в любой терминологии — мой основной тезис вообще не зависит от данного выбора.
Терминология в том, что "data race" != "все проблемы многопоточности".
_>5. Ну и наконец тот самый главный тезис: как ни называй описанную проблему, Rust никак от неё не защищает.
Rust защищает от data race.
_>>>·>В случае конкурентного чтения и записи, например, атомарость говорит о том, что читатель увидит либо старое значение, до записи, либо новое, после записи и ничто другое. Да, атомарность нужна для интов. _>>>Правильно. Только вот ассемблерная инструкция перемещения регистра в память (что равносильно оператору равенства в твоём языке программирования) является вполне себе атомарной, _>·>Для какого CPU? А про membar-ы не забыл? _>Ну если ты знаешь CPU, в котором перемещение регистра в ячейку памяти является не атомарным, то с удовольствием послушаю про такой. )))
Да, CPU сохраняет регистр атомарно, но это не "равносильно оператору равенства в твоём языке программирования", это вообще лажа. Или пункт Стандарта в студию.
И про membar-ы ты правда не знаешь?
_>А причём ты тут приплёл барьеры вообще не понятно — они работают только с переупорядочиванием инструкций, никак не влияя на их свойства.
Не только. Но и этого достаточно. Без барьеров у тебя этот global_var может так и остаться жить в регистре, например.
_>>>так что для int'ов(подразумевая под ним машинное слово) оно и так автоматически имеется. _>·>Если бы С++/Rust/etc стандарт писался ровно для одного CPU и т.п., то возможно твоё утверждение было бы и верным. _>Ну приведи хоть один контрпример. ))) Любой CPU, в котором это не так.
Если этого нет в Стандарте, то это не имеет значения. Хотя... если ты пишешь на C++ как на ассемблере-с-классами, то может и сработать.
_>>>Поэтому для ограниченного числа задач на самом деле можно параллельно обращаться к одному int'у и всё будет нормально. _>·>"А у меня всё работаииит!" _>На самом деле на многих ещё актуальных аппаратных платформах просто нет специальных атомарных инструкций, как ты думаешь, как там выживают? )))
lock-инструкциями, например. Читай std::atomic::is_lock_free() и для чего нужен std::atomic_flag. А шо делать?!..
_>>>Проблема возникает если надо использовать корректное предыдущее значение — тогда на помощь приходит CAS. _>·>А так же проблема возникает ещё в 100500 случаев, например, если этот конкретный int вдруг невыровненный оказался. _>Ну скажем в C++ он просто так невыровненным не окажется — у нас полный контроль за этим. )))
Ну т.е. невыровненным он оказаться таки может. ЧТД.
_>>>Кстати, по этой ссылке дальше идёт ещё более интересный текст про многопоточность в Rust в общем. Что никаких гарантий естественно нет и единственно чем реально озабочем тут Rust, это сохранение memory safety — как я собственно и говорил изначально. _>·>Там про race condition. _>И? О того что ты назовёшь это по другому, у тебя некорректный код вдруг заработает правильно? )))
Опятьдвадцатьпять. _Некоторый_ некорректный код, является ошибкой компиляции в rust, когда как в С++ — UB. В этом принципиальное отличие.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, alex_public, Вы писали:
_>Вот это просто феерично! Я честно не ожидал от тебя такого. Человек, который вроде как разрабатывал язык на базе C#, только лучше, оказывается не представляет как работают сопрограммы! Хотя через них работают важные функции языка (там и yield и async/await). Как ты вообще мог пользоваться этими инструментами с таким пониманием? Как чёрной магией что ли?)))
Довёл я твой код до компилируемого состояния.
using System;
using System.Threading.Tasks;
class MainClass
{
static int global_var = 0;
static async Task<int> process_asyc(int gv, int p)
{
Console.WriteLine("process_asyc begin {0} {1}", gv, p);
await Task.Delay(p);
Console.WriteLine("process_asyc end {0} {1}", gv, p);
return gv + 1;
}
static void Test(int param)
{
global_var = process_asyc(global_var, param).Result;
}
public static void Main(string[] args)
{
Console.WriteLine("Main 1");
Test(200);
Console.WriteLine("Main 2");
Test(100);
Console.WriteLine("Main 3");
}
}
результат
Main 1
process_asyc begin 0 200
process_asyc end 0 200
Main 2
process_asyc begin 1 100
process_asyc end 1 100
Main 3
Ну что? Извиняться будешь?
_>>>Правильно. Только вот ассемблерная инструкция перемещения регистра в память (что равносильно оператору равенства в твоём языке программирования) является вполне себе атомарной, так что для int'ов(подразумевая под ним машинное слово) оно и так автоматически имеется. WH>>Ещё один залёт. Не на всех платформах такое работает. _>Ну раз ты это так смело утверждаешь, то для тебя конечно же не будет проблемой назвать хоть одну платформу, на которой ассемблерная инструкция записи значения из регистра в ячейку памяти не является атомарной.
Мы тут говорим про выделенное.
Мы не знаем, какой размер регистра на целевой платформе.
Так что чтение/запись из разных потоков без атомиков это всегда UB.
А на тех платформах, где для данного типа данных можно просто писать из регистра в память так реализация атомика и сделает.
И я точно помню, что читал про платформу, где запись из регистра в память не атомарная. Сейчас искать лень.
WH>>Опять залёт. Атомики производят синхронизацию. Так что тут нет никакого одновременного доступа. _>С точки зрения ПО как раз никакой синхронизации там нет — соответствующие атомарные инструкции обращения к одной ячейке памяти без проблем запускаются одновременно на нескольких ядрах процессора. А то как разрешается этот вопрос внутри процессора (например у Intel для этого блокируется шина) — это его личное дело и может меняться в разных реализациях.
Синхронизация там есть со всех точек зрения.
А как она достигается это детали реализации.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Вот не собирался я сегодня здесь ничего писать (и так слишком много времени потратил на форум), но на это не могу не ответить, так сказать вне очереди.
_>>Вот это просто феерично! Я честно не ожидал от тебя такого. Человек, который вроде как разрабатывал язык на базе C#, только лучше, оказывается не представляет как работают сопрограммы! Хотя через них работают важные функции языка (там и yield и async/await). Как ты вообще мог пользоваться этими инструментами с таким пониманием? Как чёрной магией что ли?))) WH>Довёл я твой код до компилируемого состояния. WH>
WH>Main 1
WH>process_asyc begin 0 200
WH>process_asyc end 0 200
WH>Main 2
WH>process_asyc begin 1 100
WH>process_asyc end 1 100
WH>Main 3
WH>
WH>Ну что? Извиняться будешь?
Жесть конечно. Т.е. мало того, что ты не представляешь как оно работает внутри, так ты ещё и даже внешним интерфейсом пользоваться не умеешь. И подобные специалисты по .net ещё пытаются чему-то учить нас в C++/Rust. Просто нет слов. )))
И насладись великим откровением. ))) Кстати, в моём коде она именно такой и была изначально. )))
_>>>>Правильно. Только вот ассемблерная инструкция перемещения регистра в память (что равносильно оператору равенства в твоём языке программирования) является вполне себе атомарной, так что для int'ов(подразумевая под ним машинное слово) оно и так автоматически имеется. WH>>>Ещё один залёт. Не на всех платформах такое работает. _>>Ну раз ты это так смело утверждаешь, то для тебя конечно же не будет проблемой назвать хоть одну платформу, на которой ассемблерная инструкция записи значения из регистра в ячейку памяти не является атомарной. WH>Мы тут говорим про выделенное. WH>Мы не знаем, какой размер регистра на целевой платформе.
Бредим? Ты там случаем не спутал C++/Rust с языками типа C#/Java? На момент компиляции у языков типа C++/Rust всегда чётко известна целевая платформа, иначе компилятор просто не сможет сгенерировать код. Более того, большинство кроссплатформенных C++ библиотек меняют часть своего кода в зависимости от архитектуры целевой платформы с помощью соответствующих макросов, определяемых компилятором.
WH>Так что чтение/запись из разных потоков без атомиков это всегда UB.
UB в стандарте C++ в очень многих местах обозначает совсем не "ужас ужас, будет какие-то произвольные данные", а просто "не хотим ничего гарантировать на уровне стандарта — разбирайтесь с конкретным компилятором/платформой сами". И в данном конкретном примере на всех платформах будут происходить вполне определённые вещи.
WH>А на тех платформах, где для данного типа данных можно просто писать из регистра в память так реализация атомика и сделает. WH>И я точно помню, что читал про платформу, где запись из регистра в память не атомарная. Сейчас искать лень.
Здравствуйте, alex_public, Вы писали:
_>И насладись великим откровением. ))) Кстати, в моём коде она именно такой и была изначально. )))
Ну, врёшь же. Иди, перечитай своё сообщение.
Не была Test асинхронной.
WH>>Мы тут говорим про выделенное. WH>>Мы не знаем, какой размер регистра на целевой платформе. _>Бредим?
Ты постоянно.
_>Ты там случаем не спутал C++/Rust с языками типа C#/Java?
А на момент написания нам ничего не известно.
_>На момент компиляции у языков типа C++/Rust всегда чётко известна целевая платформа, иначе компилятор просто не сможет сгенерировать код. Более того, большинство кроссплатформенных C++ библиотек меняют часть своего кода в зависимости от архитектуры целевой платформы с помощью соответствующих макросов, определяемых компилятором.
А для того чтобы библиотеки могли это сделать нужно использовать функционал с определённым поведением.
В данном случае атомики.
_>UB в стандарте C++ в очень многих местах обозначает совсем не "ужас ужас, будет какие-то произвольные данные", а просто "не хотим ничего гарантировать на уровне стандарта — разбирайтесь с конкретным компилятором/платформой сами". И в данном конкретном примере на всех платформах будут происходить вполне определённые вещи.
На всех разные.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, WolfHound, Вы писали:
_>>И насладись великим откровением. ))) Кстати, в моём коде она именно такой и была изначально. ))) WH>Ну, врёшь же. Иди, перечитай своё сообщение. WH>Не была Test асинхронной.
. Смотрим на мой код для функции Test и видим там оператор await, который и превращает обычную функцию в сопрограмму. Ещё отмазки будут? )))
WH>>>Мы тут говорим про выделенное. WH>>>Мы не знаем, какой размер регистра на целевой платформе. _>>Ты там случаем не спутал C++/Rust с языками типа C#/Java? WH>А на момент написания нам ничего не известно.
Ууу как всё запущено. Оказывается и постоянные разговоры о мощи метапрограммирования от тебя были только для красного словца. Если ты не представляешь как решить простейшую задачу, которую уже лет 40 как элементарно записывают на убогих макросах C.
_>>На момент компиляции у языков типа C++/Rust всегда чётко известна целевая платформа, иначе компилятор просто не сможет сгенерировать код. Более того, большинство кроссплатформенных C++ библиотек меняют часть своего кода в зависимости от архитектуры целевой платформы с помощью соответствующих макросов, определяемых компилятором. WH>А для того чтобы библиотеки могли это сделать нужно использовать функционал с определённым поведением. WH>В данном случае атомики.
Или например такой https://en.cppreference.com/w/c/program/sig_atomic_t тип. ))) Да, если что, на всех моих платформах (а у меня код собирается и под сервера и под мобильники и под микроконтроллеры) это просто typedef int.
_>>UB в стандарте C++ в очень многих местах обозначает совсем не "ужас ужас, будет какие-то произвольные данные", а просто "не хотим ничего гарантировать на уровне стандарта — разбирайтесь с конкретным компилятором/платформой сами". И в данном конкретном примере на всех платформах будут происходить вполне определённые вещи. WH>На всех разные.
На всех платформах будет абсолютно одинаковое поведение: атомарные операции записи и чтения регистра в память. И я всё ещё жду от тебя пример платформы, на которой это не так (ведь это было твоё однозначное утверждение). Иначе будет очередной грандиозный слив.
Здравствуйте, ·, Вы писали:
_>>>>Ну если оно есть в Java, то ты конечно же сможешь показать мне описание этого API в официальной документации Java, да? ))) _>>·>Причём тут официальная документация? _>>Потому что если какой-то API предлагается к использованию, то он обязательно подробно описывается авторами в официальной документации. Не так ли? ) ·>Ок... И что? Какое это отношение имеет к "Полностью автоматическое и не требующее мозга для использование"? Взял скопипастил кусок кода с SO, и никакой оф-доки не надо.
Потому как данные функции являются частью языка Java в точном таком же смысле, как возможность полазить по внутренним недокументированным структурам винды (ведь каждый HANDLE — это специфический указатель) является частью языка C++. Понятна мысль? )))
_>>Вообще то я как раз утверждал полностью обратное. Я говорил что разработчику на Java (так же как и на Питоне и на других языках со сборщиком мусора) не надо знать низкоуровневых приёмов управления памятью. А ты как раз начал это опровергать, обосновывая бредом про FFI. ·>Нет, ты сказал "Полностью автоматическое и не требующее мозга для использование". Про низкоуровневость речи не было, ну допустим, отложим факты про native/unsafe т.к. они тебе не нравятся и ты хочешь их проигнорировать. ·>Но ты ещё проигнорировал и другие факты. в Java можно легко нарваться на OOM и иногда на ситуацию когда gc не справляется.
И? ) От возможности получить ООМ управление памятью перестаёт быть полностью автоматическим? )
Да, а вот если gc не справляется, то это знак что надо переходить на более подходящий для данной задачи язык, а не мучить бедную зверушку. )))
_>>Ссылки в C++ — это идеальный инструмент для передачи данных при вызове функций (т.е. вниз по стеку). В этой роли они эффективны и безопасны. Применение их для других целей практически всегда (на самом деле я не припомню вообще ни одного контрпримера, но на всякий случай оговариваюсь — мало ли какие сценарии я сходу не могу вспомнить) является не верным. ·>А если этот самый стек окажется в другом треде через магию [&], ничего? В твоём коде c mutex так и вышло. Стоит немного подрефакторить код, чтобы t.join() оказался немного в другом месте и всё нахрен улетит. Твой супер-пупер стат-ан такое найдёт?
То, что ты описал, никак не связано с ссылками или ещё чем-то — это классическая проблема разделения памяти между несколькими потоками. И в C++ для этой цели традиционно используется shared_ptr. Я не использовал его в своём тестовом примере, потому, что точно знал время жизни потоков и вообще ключевой момент примера был совсем в другом, так что не было смысла загромождать его лишним смыслом.
Ну и кстати замечу, что это был как раз пример решения задачи взаимодействия потоков через разделяемые данные, а если бы мы выбрали модель акторов, то автоматически могли бы забыть и про мьютексы и про время жизни объектов.
_>>Я смотрю ты большой знаток статических анализаторов. ))) ·>Покажи такой стат-ан, который будет _гарантировать_ следование концептуальному подходу.
На самом деле большинство серьёзных продуктов в этой области как раз так и работает — следует некой модели безопасного кода, заданной авторами. ))) Но в обсуждаемом нами вопросе такой интеллектуальный подход не нужен — достаточно обычной тупой фильтрации на ссылки вне параметров функций.
_>>·>Это опять какой-то бред. Концептуальный подход — это всё бла-бла. Вот тебе моя гениальная концепция: "Пишите программы без багов". Вот я изобрёл абсолютно безопасный язык программирования. Осталось сделать официальную документацию и готово, всем твоим критериям гарантий безопасности удовлетворяет. Нобелевку мне, можно две. _>>Непонятно к чему весь этот глупый сарказм, если вся индустрия сейчас именно так и работает. ·>Раз ты так работаешь, это ещё не повод обобщать на индустрию.
А ты хочешь сказать, что работаешь с помощью инструмента, позволяющего тебе писать код без багов? )
_>>·>И что? Это избавит, скажем, от утечек, вызваных циклическими зависимостями? Или тупо от обращений к невалидной памяти? _>>Естественно. Ну если конечно не рассматривать взаимодействие с какими-нибудь библиотеками на C и т.п., что не поддерживает безопасные инструменты C++. ·>Какие-то двойные стандарты. В С++ ты не хочешь рассматривать unsafe, а в Rust хочешь.
Я не рассматриваю FFI во всех языках, ни в C++, ни в Rust, ни в Java, ни в Python. Потому как с учётом этого и Питон будет неотличим от Ассемблера. Отдельное пояснение было потому, что некоторые "специалисты" не рассматривают C и C++ как разные языки и не считают вызов функции из C библиотеки за FFI.
_>>В ответ на мою цитату "В управление многопоточностью подобного решения пока к сожалению не существует!" (под подобным там выше явно указывалось автоматическое) ты написал длинный хвалебный текст про мьютексы в Rust'е — как это ещё можно было воспринимать? Т.е. если ты не спорил этим моим утверждением, то к чему был весь этот текст, просто ради того что бы хоть куда-то его написать? ))) ·>Т.е. ты вначале заявил глупость, потом соврал. Ок, спасибо что хоть сознался. Я с твоим утверждением не спорил, а напомнил обсуждаемый тезис. А как ещё можно было среагировать straw man fallacy?
Ну т.е. ты уже начал писать просто бессвязные монологи в ответ на мои высказывания, потому как по делу сказать было нечего?
И что ещё за обсуждаемый тезис тут был, если в данной подветке ты просто попытался придраться к моей аналогии между управлением памятью и многопоточностью?
_>>Ну очевидно же. Язык позволяет сказать компилятору: просто поверь мне, что этот тип данных безопасен для одновременного обращения из разных потоков. Так что даже если мы там нарисуем ничем не синхронизированный доступ к общей памяти, компилятор спокойно это проглотит. Понятно, что никто специально такое делать не будет, но вот допустить ошибки при реализации какого-нибудь хитрого контейнера можно без проблем... ·> А так же можно допустить ошибки в ffi или и в компиляторе. И что? Проверим компилятор статическим анализатором? А ошибки в статическом анализаторе чем проверять будем?
Причём тут код на других язык или код компилятора, если мы говорим о конкретном коде на Rust. И да, предложим что коду в стандартной библиотеке языка можно доверять (хотя на самом деле там постоянно находят уязвимости), но неужели ты хочешь сказать, что тебе ни разу не приходилось писать свой нестандартный контейнер? )))
_>>Ну если по итогу дискуссии в данной темке не останется людей, считающих что Rust решает проблемы многопоточности, то я буду считать свою миссию успешной. ) ·>Он решает _проблему_ многопоточности "data race".
Ну т.е. во-первых речь идёт уже только о маленькой части проблем многопоточности.
Во-вторых саму проблему data race решают очень по разному, в зависимости от конкретной задачи. Это могут быть и акторы и блокировки (мьютексы) и безблокировочные (на базе CAS) алгоритмы — у каждого из них свои плюсы и минусы. Все эти варианты доступны в Rust. Но они точно так же доступны и во многих других языка. Так что по твоему привнёс Rust в эту область, что ты начал считать что он "решает проблему", а другие языки нет.
_>>Нет, в Rust гарантии корректной многопоточности ни не обеспечиваются, о чём честно написано в их документации. ·>В документации конкретно написано какие гарантии обеспечиваются.
И это не гарантии корректной многопоточности, не так ли? )
_>>·>Каким образом C++ обеспечивает гарантии отсутствия утечек памяти? _>>C++ обеспечивает гарантии работы RAII (гарантии вызова деструктора). Следствием этого может быть и гарантии отсутствия утечек памяти, если применять только современные подходы к её управлению. ·>
. Смотрим на мой код для функции Test и видим там оператор await, который и превращает обычную функцию в сопрограмму. Ещё отмазки будут? )))
Нет. Не превращает.
Он даёт ошибку компиляции.
_>Ууу как всё запущено. Оказывается и постоянные разговоры о мощи метапрограммирования от тебя были только для красного словца. Если ты не представляешь как решить простейшую задачу, которую уже лет 40 как элементарно записывают на убогих макросах C.
Причем тут метапрограммирование вообще?
У тебя есть операция поведение, которое не определено.
И операция, поведение которой определено.
Иногда их поведение может совпадать.
Но зачем нужно использовать операцию с UB, когда есть операция без UB я так и не понял.
_>Или например такой https://en.cppreference.com/w/c/program/sig_atomic_t тип. ))) Да, если что, на всех моих платформах (а у меня код собирается и под сервера и под мобильники и под микроконтроллеры) это просто typedef int.
Но этот тип есть. Может авторы спецификации дураки, что сделали его? А ты один умный?
Или может быть есть платформы, где операции с int'ом не атомарны?
_>На всех платформах будет абсолютно одинаковое поведение: атомарные операции записи и чтения регистра в память.
С++ не оперирует понятием регистра.
... << RSDN@Home 1.0.0 alpha 5 rev. 0>>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Здравствуйте, alex_public, Вы писали:
_>>>Потому что если какой-то API предлагается к использованию, то он обязательно подробно описывается авторами в официальной документации. Не так ли? ) _>·>Ок... И что? Какое это отношение имеет к "Полностью автоматическое и не требующее мозга для использование"? Взял скопипастил кусок кода с SO, и никакой оф-доки не надо. _>Потому как данные функции являются частью языка Java в точном таком же смысле, как возможность полазить по внутренним недокументированным структурам винды (ведь каждый HANDLE — это специфический указатель) является частью языка C++. Понятна мысль? )))
Винда — это операционка, к языку отношения не имеющая, а Unsafe — это часть java.
Более близким аналогом для плюсов будет "полазить по std-хедерам в /usr/include". Это запрещено?
_>·>Нет, ты сказал "Полностью автоматическое и не требующее мозга для использование". Про низкоуровневость речи не было, ну допустим, отложим факты про native/unsafe т.к. они тебе не нравятся и ты хочешь их проигнорировать. _>·>Но ты ещё проигнорировал и другие факты. в Java можно легко нарваться на OOM и иногда на ситуацию когда gc не справляется. _>И? ) От возможности получить ООМ управление памятью перестаёт быть полностью автоматическим? )
Я не знаю перестаёт или нет. Ты использовал термин, но никак его не определил. Накидал каких-то удобных тебе примеров и теперь начинаешь отсеивать неудобные тебе примеры — типичное словоблудие.
_>Да, а вот если gc не справляется, то это знак что надо переходить на более подходящий для данной задачи язык, а не мучить бедную зверушку. )))
Или просто воспользоваться мозгом.
_>>>Ссылки в C++ — это идеальный инструмент для передачи данных при вызове функций (т.е. вниз по стеку). В этой роли они эффективны и безопасны. Применение их для других целей практически всегда (на самом деле я не припомню вообще ни одного контрпримера, но на всякий случай оговариваюсь — мало ли какие сценарии я сходу не могу вспомнить) является не верным. _>·>А если этот самый стек окажется в другом треде через магию [&], ничего? В твоём коде c mutex так и вышло. Стоит немного подрефакторить код, чтобы t.join() оказался немного в другом месте и всё нахрен улетит. Твой супер-пупер стат-ан такое найдёт? _>То, что ты описал, никак не связано с ссылками или ещё чем-то — это классическая проблема разделения памяти между несколькими потоками. И в C++ для этой цели традиционно используется shared_ptr.
По большому счёту в том данном коде потоки — не так важно. Ключевое что у тебя те ссылки ВНЕЗАПНО попали в другой объект , со своим временем жизни, за которым надо аккуратно следить.
_>Я не использовал его в своём тестовом примере, потому, что точно знал время жизни потоков и вообще ключевой момент примера был совсем в другом, так что не было смысла загромождать его лишним смыслом.
И тем самым в очередной раз продемострировал, что обещанный тобою "правильный современный С++" — это фикция и в реальности так не пишут, даже тот самый "один такой гений на планете". Ибо код становится невменяемым.
_>Ну и кстати замечу, что это был как раз пример решения задачи взаимодействия потоков через разделяемые данные, а если бы мы выбрали модель акторов, то автоматически могли бы забыть и про мьютексы и про время жизни объектов.
Если бы мы выбрали модель акторов, то от того кода ничего бы не осталось вообще, ибо он абсолютно бесмысленен. Это я к тому, что твои требования "перепешите мне это на раст" — неадектватны.
_>>>Я смотрю ты большой знаток статических анализаторов. ))) _>·>Покажи такой стат-ан, который будет _гарантировать_ следование концептуальному подходу. _>На самом деле большинство серьёзных продуктов в этой области как раз так и работает — следует некой модели безопасного кода, заданной авторами. )))
Таки прямо "безопасного"? Это ты в рекламной брошюрке прочитал или сам сочинил?
_>Но в обсуждаемом нами вопросе такой интеллектуальный подход не нужен — достаточно обычной тупой фильтрации на ссылки вне параметров функций.
Ага. И где на такой код можно посмотреть?
_>>>Непонятно к чему весь этот глупый сарказм, если вся индустрия сейчас именно так и работает. _>·>Раз ты так работаешь, это ещё не повод обобщать на индустрию. _>А ты хочешь сказать, что работаешь с помощью инструмента, позволяющего тебе писать код без багов? )
Нет, не хочу.
_>>>Естественно. Ну если конечно не рассматривать взаимодействие с какими-нибудь библиотеками на C и т.п., что не поддерживает безопасные инструменты C++. _>·>Какие-то двойные стандарты. В С++ ты не хочешь рассматривать unsafe, а в Rust хочешь. _>Я не рассматриваю FFI во всех языках, ни в C++, ни в Rust, ни в Java, ни в Python. Потому как с учётом этого и Питон будет неотличим от Ассемблера. Отдельное пояснение было потому, что некоторые "специалисты" не рассматривают C и C++ как разные языки и не считают вызов функции из C библиотеки за FFI.
unsafe != ffi.
В любом случае, никаких гарантий от утечек памяти нет даже в суперсовременном С++ с самыми дорогими стат-анами.
_>·>Т.е. ты вначале заявил глупость, потом соврал. Ок, спасибо что хоть сознался. Я с твоим утверждением не спорил, а напомнил обсуждаемый тезис. А как ещё можно было среагировать straw man fallacy? _>Ну т.е. ты уже начал писать просто бессвязные монологи в ответ на мои высказывания, потому как по делу сказать было нечего?
А что можно было говорить по делу, если дела у тебя не было, а просто попытка приписать ложные высказывания собеседникам.
_>И что ещё за обсуждаемый тезис тут был, если в данной подветке ты просто попытался придраться к моей аналогии между управлением памятью и многопоточностью?
Аналогия вообще ни к чему. Повторяюсь, обсуждаемый тезис — Safe Rust гарантирует отсутствие data race. Ты же начал спорить с тем, что "Safe Rust решает все проблемы многопоточности" пользуясь, понятное дело, аналогиями, ибо фактов-то нема.
_>>>Ну очевидно же. Язык позволяет сказать компилятору: просто поверь мне, что этот тип данных безопасен для одновременного обращения из разных потоков. Так что даже если мы там нарисуем ничем не синхронизированный доступ к общей памяти, компилятор спокойно это проглотит. Понятно, что никто специально такое делать не будет, но вот допустить ошибки при реализации какого-нибудь хитрого контейнера можно без проблем... _>·> А так же можно допустить ошибки в ffi или и в компиляторе. И что? Проверим компилятор статическим анализатором? А ошибки в статическом анализаторе чем проверять будем? _>Причём тут код на других язык или код компилятора, если мы говорим о конкретном коде на Rust. И да, предложим что коду в стандартной библиотеке языка можно доверять (хотя на самом деле там постоянно находят уязвимости), но неужели ты хочешь сказать, что тебе ни разу не приходилось писать свой нестандартный контейнер? )))
Приходилось, конечно. Про rust я ничего не скажу, но скажем native-обёртки, misc.Unsafe попадались. Этого кода немного, доли процента от размера всего проекта и он проверяется очень внимательно, многими людьми и многократно, поэтому есть доверие. Зато после этого можно уверенно писать тонны кода бизнес-логики не включая мозг.
В этом и отличие от С++, когда ты начал писать код в стиле "не было смысла загромождать его лишним смыслом", а потом внезапно в проде что-то летит к чертям.
_>>>Ну если по итогу дискуссии в данной темке не останется людей, считающих что Rust решает проблемы многопоточности, то я буду считать свою миссию успешной. ) _>·>Он решает _проблему_ многопоточности "data race". _>Ну т.е. во-первых речь идёт уже только о маленькой части проблем многопоточности.
Об этом речь всегда и шла в этом топике.
_>Во-вторых саму проблему data race решают очень по разному, в зависимости от конкретной задачи. Это могут быть и акторы и блокировки (мьютексы) и безблокировочные (на базе CAS) алгоритмы — у каждого из них свои плюсы и минусы. Все эти варианты доступны в Rust. Но они точно так же доступны и во многих других языка. Так что по твоему привнёс Rust в эту область, что ты начал считать что он "решает проблему", а другие языки нет.
Да, другие тоже решают, но Safe Rust не только решает, но и _гарантирует_ отсутствие data race, а C/C++ нет. В Java например, этим заведует java memory model (которую потом частично стырили в C++), но java язык другого класса, поэтому сравнивать бесмысленно. Про другие языки вообще сказали, что обсуждение не идёт, т.к. rust напрямую конкурирует с C++.
_>>>Нет, в Rust гарантии корректной многопоточности ни не обеспечиваются, о чём честно написано в их документации. _>·>В документации конкретно написано какие гарантии обеспечиваются. _>И это не гарантии корректной многопоточности, не так ли? )
Нет, конечно. Гарантии корректной многопоточности были только в твоём воображении.
_>>>·>Каким образом C++ обеспечивает гарантии отсутствия утечек памяти? _>>>C++ обеспечивает гарантии работы RAII (гарантии вызова деструктора). Следствием этого может быть и гарантии отсутствия утечек памяти, если применять только современные подходы к её управлению. _>·> _>Сказать больше нечего? ) Понимаю... )))
А что тут говорить? RAII есть и в rust по полной, и даже чуть больше. Но ведь ты не пытаешься даже понять проблему.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Здравствуйте, WolfHound, Вы писали:
WH>Но этот тип есть. Может авторы спецификации дураки, что сделали его? А ты один умный?
Исторически/изначально Unix работал на 16-тибитной архитектуре.
Но int там был тоже 16 бит. ))
Думаю, авторы перестраховались, потому что сам язык Си был тогда наколенной поделкой аккурат для одновременно с ним разрабатываемого Unix.
Куда бы Си развивался дальше — не понятно, т.е. вряд ли авторы заранее предполагали, что ширина int в будущем станет не детерминированной, т.е. платформенно-зависимой.
Здравствуйте, ·, Вы писали:
·> ·>Ты похоже не способен воспринимать текст длиннее одного предложения. Теперь ты забыл слова "Safe Rust". Воткнуть Send/Sync как попало куда попало можно только с unsafe.
Так, давай раз и навсегда разберёмся что такое unsafe в Rust. Это вовсе не обязательно какой-то FFI или низкоуровневая магия. Это любой код, который нарушает требования BC, но всё же должен быть написан. И сюда относятся в том числе и все виды контейнеров с доступом из нескольких потоков: и инструменты синхронизации (те самые Mutex, Atomic и т.п.) и различные массивы и всё остальное (можешь посмотреть на часть списка здесь https://doc.rust-lang.org/stable/std/marker/trait.Sync.html в разделе Implementors). Т.е. все подобные инструменты и в стандартной библиотеки языка и в твоём личном коде априори написаны unsafe, просто потому что иначе невозможно. А вот использование этих инструментов в своём коде уже не требует никакого unsafe...
В итоге твоя фраза типа "Rust гарантирует отсутствие date race вне unsafe блоков" переводится на практике как "Rust гарантирует отсутствие date race вне участков кода занятых собственно реализацией многопоточного доступа". Подразумевая, что этому коду (где собственно и возможны основные ошибки многопоточности) мы верим на слово.
Например вот такой код:
fn main() {
let val = Arc::new(MyBox{x: 0});
for _ in 0..10 {
let val = Arc::clone(&val);
thread::spawn(move || {
*val.get()+=1;
println!("{:?}", val.x);
});
}
thread::sleep(time::Duration::from_millis(100));
}
Может быть как корректным, так и нет, в зависимости от реализации MyBox. Но компилятор пропустит его в любом случае без всяких возражений, даже для такого некорректного в многопоточности MyBox:
struct MyBox<T> {x: T}
impl<T> MyBox<T> {fn get(&self)->&mut T {unsafe {&mut *(self as *const MyBox<T> as *mut T)}}}
И кстати такой код даже будет кое-как работать, хотя и выдавая каждый раз разное. )))
Если же реализовать MyBox через Mutex (который кстати внутри тоже чистый unsafe), то код будет полностью корректным. Но это знаем мы, а для компилятора разницы нет — всё по прежнему на доверие к чужому/своему коду.