Здравствуйте, Mamut, Вы писали:
Ф>>Зачем всё это? (зачем управлять потоками) Ф>>Почему нельзя положиться на рантайм — пусть он управляет.
M>Действительно, ведь рантайм наделен телепатической силой и знает о том, что программист хочет сделать.
Объясни рантайму, что ты хочешь.
Хочешь ты наверняка не потоками управлять, а задачу (в фоне) выполнить. Так и создавай задачу, например вот так:
// Create and start the task in one operation.var taskA = Task.Factory.StartNew(() => Console.WriteLine("Hello from taskA."));
Задача — это не поток, задача может использовать отдельный поток, но не обязательно. Задачи ты можешь комбинировать (сцеплять, задавать зависимости между ними), отменять отдельные задачи — совершенно другой уровень.
Ф>>Я даже в абсолютном большинстве ситуаций не вижу смысла "запускать поток" — в пень, не хочу ни создавать ещё один, лишний, объект, ни следить за его временем жизни.
M>
Зачем?
Ф>>Что за глупость убивать поток? Ф>>Когда это может быть необходимо? Что делать с оставшейся программой, у которой убили поток? Ф>>И слава богу! Ф>>Убивать нативные потоки — очень-очень плохая идея (читай классиков). Убийство managed потоков — тоже не есть здравая мысль, почти по тем же причинам. Ф>>Для того чтобы иметь возможность безболезненно убить MANAGED поток нужно иметь возможность гарантировать, что он в процессе своего выполнения не зааффектил ничего из внешнего мира.
M>Простейший пример, который я чуть ли не в каждой первой десктопной прграмме: фоновые задачи.
Ты правильные термины начинаешь использовать.
M>Например, всякие IDE индексируют файлы для поиска, компилируют программы и т.п. в отдельных потоках, не влияющих на GUI-поток. Ты не поверишь, но возникает необходимость выполнение этих задач прекратить. И таких примеров (управление потоками и прочее, что я описал) сотни.
Всё было бы просто, если бы задачи были совсем независимы, по данным, но это не про реальную жизнь: в реальной жизни независимых задач крайне мало, и с ними всё решается достаточно просто, например, с помощью Parallel.ForEach(), который в составе параметров принимает в том числе и CancellationToken.
Повторюсь: убивать поток — плохая идея. Даже с процессом может плохо получиться:
при грубом завершении потока тебе нужно как минимум файлы закрыть (и, кстати, закрывать ли — тот ещё вопрос), а ещё лучше — объекты синхронизации в правильное состояние вернуть — вот это уже совсем не простая задача, которая в общем случае не решается.
Для прекращения выполнения кода существуют другие методы. Самый удачный, на мой взгляд — реакция самой задачи на CancellationToken.
Может быть ты знаешь какую-то магию, которую я не знаю в этой области? Как эта проблема решается в Erlang'е?
Всё сказанное выше — личное мнение, если не указано обратное.
Re[3]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, Mamut, Вы писали:
M>>>Все просто. Мы запустили поток, нам нужно: M>>>- Знать, что поток выполняется M>>>- Знать, когда поток завершится, и узнать, как поток завершился (вернул значение, вылетел с ошибкой) M>>>- Возможно, перезапустить поток, если он завершился M>>>- Убить поток, если это необходимо Ф>>Зачем всё это? (зачем управлять потоками) M>Тут наш коллега Ikemefula спрашивает, почему я считаю, что люди не читают, что я пишу. Ну а как еще говорить, если пишешь, зачем управлять потоками, и тут же тебя спрашивают: а зачем управлять потоками? M> ... M>Остальное все поскипано, потому что надоело по пятидесятому кругу что-то объяснять людям, которые предпочитают отвечать, не читая и даже не потратив и секунды на размышление.
1. С терпеливостью удава ждём кого-то кто действительно не читал тему
2. Стремительный бросок — "Я же говорил!"
3. Чрезмерное обобщение — "Оппоненты не читают!"
4. ???
5. PROFIT! Demagogy wins!
Здравствуйте, Mamut, Вы писали:
I>>Есть очень четкая корреляция — тобой написаное хорошо понимают в основном люди с опытом в эрланге. Это демонстрирует, что ты объясняешь как будто сам себе.
M>Есть очень четкая корреляция: люди не понимают написанное даже если там больше половины текста про Эрланг вообще не упоминает. Ты, например, тому тоже яркий пример
"больше половины текста про Эрланг вообще не упоминает" — это ты про своё сообщение, где у тебя в каждой строчке "Эрланг" ?
Re[4]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, Ikemefula, Вы писали:
I>Если бы ты проявил пример чистоплотности, при чем с употреблением терминов "конкурентный", "параллельный", все было бы горадо проще.
слово "конкурентный" появилось-то в нашем лексиконе совсем недавно. и то, как более точное описание проблем многопоточных приложений.
а раньше никого это не волновато. и никто не умер.
I>Ты же почему то отказываешься использовать точные термины, вместо этого используешь сомнительные примеры, или слишком общие термины "многопоточно, многоядерно, распределено"
три разные ситуации. как их можно связывать с параллельностью?
...coding for chaos...
Re[5]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, neFormal, Вы писали:
F>слово "конкурентный" появилось-то в нашем лексиконе совсем недавно. и то, как более точное описание проблем многопоточных приложений. F>а раньше никого это не волновато. и никто не умер.
Раньше вообще не знали о программировании и ничего, жили люди. Даже до изобретения колеса тоже как то справлялись.
I>>Ты же почему то отказываешься использовать точные термины, вместо этого используешь сомнительные примеры, или слишком общие термины "многопоточно, многоядерно, распределено"
F>три разные ситуации. как их можно связывать с параллельностью?
В том то и дело, что три разные. Мамут ничего внятного здесь не говорит. Между тем, параллельное программирование оно тоже и многопоточно, и многоядерно и распределено. И конкурентное так же многопоточно, многоядерно и распределено.
Здравствуйте, neFormal, Вы писали:
Ф>>Что делать с оставшейся программой, у которой убили поток?
F>ПА-НИ-КО-ВАТЬ! ААА!
Правильно, рубить процесс к чертям, ибо он в неконсистентном состоянии. Именно так и делают языки для нативного кода.
Ф>>Убивать нативные потоки — очень-очень плохая идея (читай классиков). Убийство managed потоков — тоже не есть здравая мысль, почти по тем же причинам. F>но это же проблема реализации, а не идеи
Нет, сама идея убога.
Ф>>...нужно иметь возможность гарантировать, что он в процессе своего выполнения не зааффектил ничего из внешнего мира. Даже с процессами не всегда такое возможно.
F>ну да, всё верно. F>если технология не позволяет освобождать внешние связи, то всё плохо.
Проблема не в технологии, а в том, что в общем случае ты не знаешь что и в какое состояние нужно всё возвращать. Простой пример: нужно ли закрывать файл, открытый в другом потоке? Может быть его удалить нужно?
Ещё пример: кому должен принадлежать мютекс, после принудительного завершения потока?
Ф>>Не имеешь ли ты ввиду часом то, что имели ввиду авторы Smalltalk'а, когда говорили об "общении" между объектами? Если да, то это плохая идея.
F>да, именно, как в ST. с этой стороны эрланг можно назвать даже объектно-ориентированным языком. с известной долей условности. F>а чем идея плоха?
Это отдельная тема — потом отвечу (если не забуду). Если вкратце, то плохо это потому, что такой подход плодит ошибки в процессе разработки, которые могли бы быть исправлены до компиляции программы.
Всё сказанное выше — личное мнение, если не указано обратное.
Здравствуйте, Ikemefula, Вы писали:
I>Раньше вообще не знали о программировании и ничего, жили люди. Даже до изобретения колеса тоже как то справлялись.
и что же изменилось к лучшему с новым термином?
F>>три разные ситуации. как их можно связывать с параллельностью? I>В том то и дело, что три разные. Мамут ничего внятного здесь не говорит. Между тем, параллельное программирование оно тоже и многопоточно, и многоядерно и распределено. И конкурентное так же многопоточно, многоядерно и распределено.
ну да, и чо? какая вообще разница?
...coding for chaos...
Re[4]: Эрланг и все-все-все (на самом деле, не совсем)
Ф>>>Зачем всё это? (зачем управлять потоками) Ф>>>Почему нельзя положиться на рантайм — пусть он управляет.
M>>Действительно, ведь рантайм наделен телепатической силой и знает о том, что программист хочет сделать.
Ф>Объясни рантайму, что ты хочешь.
О. Внезапно. То есть уже надо управлять потоками.
Ф>Хочешь ты наверняка не потоками управлять, а задачу (в фоне) выполнить. Так и создавай задачу, например вот так: Ф>Задача — это не поток, задача может использовать отдельный поток, но не обязательно. Задачи ты можешь комбинировать (сцеплять, задавать зависимости между ними), отменять отдельные задачи — совершенно другой уровень.
Каким образом это я буду делать, если мне ни язык ни библиотеки не предоставляют никакой возможности это делат, кроме как устраивать закаты вручную?
M>>Простейший пример, который я чуть ли не в каждой первой десктопной прграмме: фоновые задачи. Ф>Ты правильные термины начинаешь использовать.
Я их изначально правильные использую. Просто для людей, у которых в руках нет никаких инструментов для работы со всем этим, это кажется разными задачами
M>>Например, всякие IDE индексируют файлы для поиска, компилируют программы и т.п. в отдельных потоках, не влияющих на GUI-поток. Ты не поверишь, но возникает необходимость выполнение этих задач прекратить. И таких примеров (управление потоками и прочее, что я описал) сотни.
Ф>Всё было бы просто, если бы задачи были совсем независимы, по данным, но это не про реальную жизнь: в реальной жизни независимых задач крайне мало, и с ними всё решается достаточно просто, например, с помощью Parallel.ForEach(), который в составе параметров принимает в том числе и CancellationToken.
Да-да. Форич, расшариваемые токены, к которым доступ идет через lock(object). Бггггг. В общем все ровно то, о чем я говорю. Но потом удивляются, что я говорю, что люди вообще даже не прилагают усилий к тому, чтобы прочитать написанное
Ф>Для прекращения выполнения кода существуют другие методы. Самый удачный, на мой взгляд — реакция самой задачи на CancellationToken.
Ф>Может быть ты знаешь какую-то магию, которую я не знаю в этой области? Как эта проблема решается в Erlang'е?
В Эрланге это решается шатными средствами: в любой поток(он же процесс он же задача) можно послать сообщение, которое поток/задача/процесс может обработать и завершиться штатными средствами. Для более развесистых конструкций ест опять же штатные средства (в 90% случаев ничего делать не надо, для чистки ресурсов достаточно реализовать подчистку в функции terminate).
Здравствуйте, Философ, Вы писали:
Ф>>>Что делать с оставшейся программой, у которой убили поток? F>>ПА-НИ-КО-ВАТЬ! ААА! Ф>Правильно, рубить процесс к чертям, ибо он в неконсистентном состоянии. Именно так и делают языки для нативного кода.
я что-то не понял, почему.
поток убили снаружи? а его в этот момент использовали?
Ф>>>Убивать нативные потоки — очень-очень плохая идея (читай классиков). Убийство managed потоков — тоже не есть здравая мысль, почти по тем же причинам. F>>но это же проблема реализации, а не идеи Ф>Нет, сама идея убога.
аргументы?
Ф>Проблема не в технологии, а в том, что в общем случае ты не знаешь что и в какое состояние нужно всё возвращать. Простой пример: нужно ли закрывать файл, открытый в другом потоке? Может быть его удалить нужно?
почему не знаю? файл нужно закрыть, если он был открыт.
Ф>Ещё пример: кому должен принадлежать мютекс, после принудительного завершения потока?
не этому потоку.
F>>да, именно, как в ST. с этой стороны эрланг можно назвать даже объектно-ориентированным языком. с известной долей условности. F>>а чем идея плоха? Ф>Это отдельная тема — потом отвечу (если не забуду). Если вкратце, то плохо это потому, что такой подход плодит ошибки в процессе разработки, которые могли бы быть исправлены до компиляции программы.
блин, я, можно сказать, только ради этой темы сюда и влез, а ты вот как значит, да?
I>>>Есть очень четкая корреляция — тобой написаное хорошо понимают в основном люди с опытом в эрланге. Это демонстрирует, что ты объясняешь как будто сам себе. M>>Есть очень четкая корреляция: люди не понимают написанное даже если там больше половины текста про Эрланг вообще не упоминает. Ты, например, тому тоже яркий пример I>"больше половины текста про Эрланг вообще не упоминает" — это ты про своё сообщение, где у тебя в каждой строчке "Эрланг" ?
Во-во. Ровно то, о чем я говорю. Ни в вводной части ни в «вниз по кроличьей норе» нет ни слова про Эрланг. И часть «Но как же библиотеки и Эрланг не нужен?» тоже не про Эрланг, хоть он там и упоминается. Но ты же даже не притворяешься, что перед тобой стоит задача понять, что там написано.
Здравствуйте, neFormal, Вы писали:
Ф>>Правильно, рубить процесс к чертям, ибо он в неконсистентном состоянии. Именно так и делают языки для нативного кода.
F>я что-то не понял, почему. F>поток убили снаружи? а его в этот момент использовали?
Он использовал.
Или не использовал — ты в общем случае не знаешь, поэтому можешь перекреститься, и надеяться на то, что не использовал.
Ф>>>>Убивать нативные потоки — очень-очень плохая идея (читай классиков). Убийство managed потоков — тоже не есть здравая мысль, почти по тем же причинам. F>>>но это же проблема реализации, а не идеи Ф>>Нет, сама идея убога.
F>аргументы?
Ф>>Проблема не в технологии, а в том, что в общем случае ты не знаешь что и в какое состояние нужно всё возвращать. Простой пример: нужно ли закрывать файл, открытый в другом потоке? Может быть его удалить нужно? F>почему не знаю? файл нужно закрыть, если он был открыт.
А если создан, стало быть, удалить? Бгг ))
Ф>>Ещё пример: кому должен принадлежать мютекс, после принудительного завершения потока? F>не этому потоку.
А кому? — В этом ведь весь вопрос.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[5]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, neFormal, Вы писали:
I>>Если бы ты проявил пример чистоплотности, при чем с употреблением терминов "конкурентный", "параллельный", все было бы горадо проще. F>слово "конкурентный" появилось-то в нашем лексиконе совсем недавно. и то, как более точное описание проблем многопоточных приложений.
Это описание частных проблем многопоточных приложений, то есть некоторый подкласс задач.
F>а раньше никого это не волновато. и никто не умер.
, не догадывается о существовании целого класса "параллельных" задач, и всё время говорит об конкуретных, при этом используя неверные термины (например называя конкуретность параллельностью — на что ему уже многократно указывали).
Re[7]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, neFormal, Вы писали:
I>>Раньше вообще не знали о программировании и ничего, жили люди. Даже до изобретения колеса тоже как то справлялись.
F>и что же изменилось к лучшему с новым термином?
Разные задачи, что очевидно, решаются по разному.
F>>>три разные ситуации. как их можно связывать с параллельностью? I>>В том то и дело, что три разные. Мамут ничего внятного здесь не говорит. Между тем, параллельное программирование оно тоже и многопоточно, и многоядерно и распределено. И конкурентное так же многопоточно, многоядерно и распределено.
F>ну да, и чо? какая вообще разница?
"потоками надо управлять и потокам надо общаться друг с другом" — в каждом из случаев это делается по разному. Мамут вещает ровно про один, только нигде про это явно не говорит.
Сравнение с другими инструментами:
"Какие инструменты предлагает нам подавляющее большинство современных языков программирования для решения этих двух вопросов? Никакие. Нет таких инструментов."
"Для подавляющего большинства библиотек (в том числе и приведенных в этом топике) на эти вопросы нет ответа"
и тут же "не знаю, не пробовал, не в курсе, пробовал но давно"
Диссонанс какой то. Что бы сравнивать и утверждать "там ничего нет", надо бы ознакомиться что же "там".
Re[6]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, Философ, Вы писали:
F>>я что-то не понял, почему. F>>поток убили снаружи? а его в этот момент использовали? Ф>Он использовал. Ф>Или не использовал — ты в общем случае не знаешь, поэтому можешь перекреститься, и надеяться на то, что не использовал.
кого же он использовал или не использовал?
F>>аргументы?
я всё ещё надеюсь их увидеть.
Ф>>>Проблема не в технологии, а в том, что в общем случае ты не знаешь что и в какое состояние нужно всё возвращать. Простой пример: нужно ли закрывать файл, открытый в другом потоке? Может быть его удалить нужно? F>>почему не знаю? файл нужно закрыть, если он был открыт. Ф>А если создан, стало быть, удалить? Бгг ))
зачем? тоже закрыть. это не транзакции, даже не надейся.
Ф>>>Ещё пример: кому должен принадлежать мютекс, после принудительного завершения потока? F>>не этому потоку. Ф>А кому? — В этом ведь весь вопрос.
а кому принадлежит мутех после того, как процесс его освободит? вот и здесь так же.
...coding for chaos...
Re[6]: Эрланг и все-все-все (на самом деле, не совсем)
, не догадывается о существовании целого класса "параллельных" задач, и всё время говорит об конкуретных, при этом используя неверные термины (например называя конкуретность параллельностью — на что ему уже многократно указывали).
что и как от этого изменилось в дискуссии?
...coding for chaos...
Re[8]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, Ikemefula, Вы писали:
I>>>Раньше вообще не знали о программировании и ничего, жили люди. Даже до изобретения колеса тоже как то справлялись. F>>и что же изменилось к лучшему с новым термином? I>Разные задачи, что очевидно, решаются по разному.
поясни же
параллельные задачи — ...
конкурентные задачи — ...
I>"потоками надо управлять и потокам надо общаться друг с другом" — в каждом из случаев это делается по разному. Мамут вещает ровно про один, только нигде про это явно не говорит.
я всё ещё не понимаю принципиальной разницы.
I>Диссонанс какой то. Что бы сравнивать и утверждать "там ничего нет", надо бы ознакомиться что же "там".
Здравствуйте, Mamut, Вы писали:
M>Во-во. Ровно то, о чем я говорю. Ни в вводной части ни в «вниз по кроличьей норе» нет ни слова про Эрланг. И часть «Но как же библиотеки и Эрланг не нужен?» тоже не про Эрланг, хоть он там и упоминается. Но ты же даже не притворяешься, что перед тобой стоит задача понять, что там написано.
Угу, сообщение совсем, совсем не про Эрланг: M> И я утверждаю, что именно потому, что Erlang предоставляет комплексное решение возникающих из требований проблем, он заруливает подавляющее большинство других языков программирования именно в подходах к многопоточному программированию.
Оно, конечно, не в прологе, но ведь сами же настаиваете: M> Убедительная просьба сначала прочитать, а потом отвечать
Вот после прочтения всего и делается вывод об Эрланге.
И ладно бы вы привели бы сравнения реализации одних и тех же задач на разных языках, и показали что в Эрланге это потенциально лучше/надёжнее и в каких случаях это стоит использовать.
Вместо этого сначала выдвигается требование "Все возможности должны быть в спецификации стандарта языка", видимо без такого handicap'а Эрлангу вообще не светит: М>"Давайте посмотрим, что у нас считается state of the art в некоторых современных языках. Для тех, кто в наглухо забронированном танке. Это то, что доступно в языках на уровне стандартов и стандартных библиотек",
Затем в ход идёт очень странная индукция: "В с++ есть потоки, мьютексы. Ну еще есть futures." -> "C#? Objective-C? Ruby? Php бггг? кто там еще есть в индексе? Все то же самое и одно и то же.".
(Эта индукция сама по себе показательна, поэтому я не буду заострять внимания на C# async/await, TPL — которые как раз входят в стандартную библиотеку)
"Вниз по кроличьей норе"
Абсурд достигает пика: M> Какие инструменты предлагает нам подавляющее большинство современных языков программирования для решения этих двух вопросов? Никакие. Нет таких инструментов. Вплоть до смешного: M> “There is no portable way in C++11 (that I'm aware of) to non-cooperatively kill a single thread in a multi-thread program (i.e. without killing all threads)."
Опять та же самая альтернативная логика. И да, с какого перепугу жёсткое прерывание потока (non-cooperatively kill a single thread) должно входить в стандарт, если его убирают даже из тех языков, где оно первоначально было?
Остальная часть так-же изобилует отсутствием конкретики (по крайней мере там, гда не упоминается Э.).
ARI ARI ARI... Arrivederci!
Re[9]: Эрланг и все-все-все (на самом деле, не совсем)
Здравствуйте, neFormal, Вы писали:
I>>>>Раньше вообще не знали о программировании и ничего, жили люди. Даже до изобретения колеса тоже как то справлялись. F>>>и что же изменилось к лучшему с новым термином? I>>Разные задачи, что очевидно, решаются по разному. F>поясни же F>параллельные задачи — ... F>конкурентные задачи — ...
В этом и предыдущем топике уже многократно поясняли, причём разные участники. Если на пальцах, то:
* При параллельном программировании главная цель состоит в том, чтобы как можно быстрее получить результат, и достигается это путём задействования всех доступных вычислительных ресурсов (грубо говоря всех ядер). Железные потоки/процессы здесь — это необходимость, а не средство упрощения кода. Более того, однопоточный вариант является самым простым, но естественно не задействует все вычислительные ресурсы.
В самом коде обычно нет никакого явного управления потоками и взаимодействия между ними. Всё это прекрасно скрывается внутри библиотек типа TBB, PPL, Thrust и различных реализаций MapReduce — предоставляя пользователю параллельные примитивы типа parallel_transform, parallel_foreach, parallel_for и parallel_reduce.
Конкретный пример — оптимизационная задача: есть набор параметров, ограничения и целевая функция. Необходимо найти значения параметров соответствующие минимуму/максимуму целевой функции. Вычисление значений целевой функции для разных параметров можно выполнять параллельно в разных вычислительных потоках, улучшая утилизацию железа.
* При конкурентом программировании сама задача решается на порядки проще с использованием процессов, нежели без них. То есть процессы здесь это не необходимость, а благо. Причём процессы здесь необязательно железные, даже более того — всё может работать в одном железном потоке.
Каноничный пример: сетевой сервер обслуживающий клиентов проще всего выражается через модель наподобие "один логический поток на одного клиента".
(Вообще говоря и параллельное и конкурентное программирование охватывают бОльшие области чем я описал, но для первого приближения такого описания достаточно)
S>Опять та же самая альтернативная логика. И да, с какого перепугу жёсткое прерывание потока (non-cooperatively kill a single thread) должно входить в стандарт, если его убирают даже из тех языков, где оно первоначально было?
S>Остальная часть так-же изобилует отсутствием конкретики (по крайней мере там, гда не упоминается Э.).
Специально ткну тебя носом:
Если у тебя однопоточное приложение — проходим мимо. Как только потоков становится N > 1, все становится очень печально (в большинстве языков). Потому что потоками надо управлять и потокам надо общаться друг с другом.
Что такое управлять потоками?
Все просто. Мы запустили поток, нам нужно:
— Знать, что поток выполняется
— Знать, когда поток завершится, и узнать, как поток завершился (вернул значение, вылетел с ошибкой)
— Возможно, перезапустить поток, если он завершился
— Убить поток, если это необходимо
Что такое общаться друг с другом?
Все просто. Часто одному потоку надо знать о промежуточном состоянии другого потока или передать в другой поток какое-то свое промежуточное состояние. Как простейшие примеры:
— UI поток должен узнать, что что-то изменилось в долгоиграющем потоке, чтобы обновить свое состояние (прогрессбар, например)
— Долгоиграющий поток должен узнать, что что-то изменилось в окружающем мире, чтобы продолжить работу (появились новые данные, изменилась конфигурация и т.п.)
Какие инструменты предлагает нам подавляющее большинство современных языков программирования для решения этих двух вопросов?
У тебя есть пробелмы с пониманием написанного? Нет? Хорошо. Ответь на поставленный в конце вопрос.
Только не надо мне рассказывать сказки про то, что тебе не надо ничем управлять и ни с чем общаться.
Здравствуйте, Evgeny.Panasyuk, Вы писали:
EP>* При параллельном программировании главная цель состоит в том, чтобы как можно быстрее получить результат EP>* При конкурентом программировании сама задача решается на порядки проще
т.е. разница лишь в количестве потоков относительно процессоров и нагрузке на каждый поток.
негусто, если честно.