может ли c / c ++ выполнять вытесняющую многозадачность в одном потоке?

Вытесняющая многозадачность в C / C ++: может ли выполняющийся поток прерываться каким-либо таймером и переключаться между задачами?

Многие виртуальные машины и среды выполнения на других языках, использующие «зеленые» потоки и тому подобное, реализованы в этих условиях; могут ли приложения C / C ++ делать то же самое?

Если да, то как?

Это будет зависеть от платформы, поэтому, пожалуйста, обсудите это с точки зрения поддержки конкретных платформ для этого; например если есть какая-то магия, которую вы можете сделать в SIGALRM обработчике в Linux, чтобы поменять местами какой-то внутренний стек (возможно, используя longjmp?), это было бы здорово!


Я спрашиваю, потому что мне любопытно.

Я несколько лет работал над созданием асинхронных циклов ввода-вывода. При написании асинхронных циклов ввода-вывода я должен быть очень осторожен, чтобы не помещать в цикл дорогостоящие вычисления, поскольку это приведет к DOS цикла.

Поэтому меня интересуют различные способы заставить асинхронный цикл ввода-вывода восстанавливать или даже полностью поддерживать какой-то зеленый поток или такой подход. Например, выборка активной задачи и количества итераций цикла в SIGALRM, а затем, если обнаруживается, что задача блокируется, переместите все остальное в новый поток или какой-нибудь хитрый вариант этого с желаемым результатом.

В связи с этим недавно были некоторые жалобы на node.js, и в других местах я видел дразнящие комментарии о других средах выполнения, таких как Go и Haskell < / а>. Но давайте не будем уходить слишком далеко от основного вопроса о том, можете ли вы выполнять вытесняющую многозадачность в одном потоке на C / C ++.


person Will    schedule 30.10.2011    source источник
comment
Как вы думаете, на каком языке обычно пишут виртуальные машины?   -  person Mat    schedule 30.10.2011
comment
В общем, это то, что делают будильники. Итак, если у вас есть будильники, вопрос сводится к тривиальному, не так ли?   -  person littleadv    schedule 30.10.2011
comment
да - такое поведение можно реализовать на C / C ++. Какой у вас конкретный вопрос? Что ты пробовал? Что не работает? пожалуйста, покажите какой-нибудь исходный код ...   -  person Yahia    schedule 30.10.2011
comment
так может ли сигнал тревоги изменить стек возврата? Как?   -  person Will    schedule 30.10.2011
comment
@will, alarm может изменить стек, если вы действительно хотите возиться (вам придется написать этот код), но обычно люди меняют изменчивую переменную и время от времени проверяют ее, принимая соответствующие решения.   -  person littleadv    schedule 30.10.2011
comment
изменение стека проблематично с точки зрения разрешений ... вы работаете как root?   -  person Yahia    schedule 30.10.2011
comment
littleadv, не могли бы вы подробнее рассказать, как выполнять упреждающую многозадачность? Проверка переменных - это определение совместной многозадачности.   -  person Will    schedule 30.10.2011
comment
@Will: вещь, на которой запущена ваша виртуальная машина (VirtualBox, qemu, что угодно), скорее всего, будет написана на C или C ++ или на том и другом. Ядро вашей операционной системы также, скорее всего, будет написано на C или C ++, или на том и другом.   -  person Mat    schedule 30.10.2011
comment
@Will - если вы хотите написать планировщик для своих потоков - вам нужно будет фактически сохранить и восстановить все регистры (включая счетчик процесса, путем перехода к адресу следующего запланированного потока) и stack, что, конечно, можно сделать с помощью сигналов тревоги, но, как уже упоминалось, у вас возникнет проблема с разрешениями. Зачем нужно изобретать велосипед? Я думаю, вам следует добавить к своему вопросу актуальную проблему, которую вы пытаетесь решить, иначе она должна (и, вероятно, будет) закрыта.   -  person littleadv    schedule 30.10.2011
comment
@Yahia представьте, что я работаю на win32 или iOS. Если на некоторых платформах возникают проблемы с разрешениями, опубликуйте ответ, в котором объясняется, как это можно сделать и какие разрешения необходимы на этой платформе ...   -  person Will    schedule 30.10.2011
comment
Я не понимаю, почему люди продолжают голосовать, чтобы закрыть это; самая первая версия вопроса была законным, полностью автономным вопросом, даже если люди хотят ответить «нет, вы не можете». Я не особо огорчен, но разочарован; кто-то может действительно знать, как это сделать, или иметь значимые идеи о том, как превратить голодные задачи в новый подход, который я описал, или другое нестандартное мышление.   -  person Will    schedule 30.10.2011
comment
@Will: Вопрос кажется разумным, особенно с точки зрения встроенной системы, где может потребоваться определенный уровень упреждающего задания, но не нужны накладные расходы на полноценную упреждающую ОС. В общем, я бы не одобрил доморощенные решения для упреждающей многозадачности в пользу комбинации прерываний и совместной многозадачности. Если переключение задач выполняется подпрограммой taskswitch (), которая вызывается как обычная функция, обычно требуется сохранить только те регистры, которые явно указаны в документации компилятора. Если переключение задач выполняется через прерывание ...   -  person supercat    schedule 01.11.2011
comment
... тогда нужно сохранить и восстановить любой регистр, который может быть использован где угодно. Если у конкретного процессора есть некоторые специальные регистры или флаги, о которых знает компилятор, а у автора многозадачности нет, одна задача может повредить регистры или флаги, используемые другим, поскольку многозадачник не будет знать, что нужно хранить отдельный копировать для каждого контекста. В некоторых встроенных системах ситуация может быть еще хуже, поскольку в некоторых микросхемах есть такие вещи, как блоки умножения-ускорителя, состояние которых не может быть адекватно считано и восстановлено.   -  person supercat    schedule 01.11.2011
comment
@supercat спасибо за мысль. Верно. В настоящее время я предпочитаю поток-наблюдатель, который, обнаружив, что цикл io слишком долго застревает в той же задаче на той же итерации, помещает цикл io в новый свежий поток и оставляет задачу еще некоторое время для изящного завершения. Он будет полагаться на сигналы и блокировки, но не будет необходимости изменять контекст или возвращать адреса от обработчика сигналов. Возможно, если вы проголосуете за повторное открытие, вы сможете добавить ответ? ;)   -  person Will    schedule 01.11.2011


Ответы (6)


В Windows есть волокна, которые представляют собой запланированные пользователем единицы выполнения, совместно использующие один и тот же поток. http://msdn.microsoft.com/en-us/library/windows/desktop/ms682661%28v=vs.85%29.aspx.

UPD: дополнительную информацию о переключении контекста по расписанию можно найти в источниках LuaJIT, он поддерживает сопрограммы для разных платформ, поэтому просмотр источников может быть полезен, даже если вы вообще не используете lua. Вот краткое изложение: http://coco.luajit.org/portability.html,

person Denis K    schedule 30.10.2011
comment
так что кооперативная потоковая передача - мыслящие сопрограммы - и не вытесняющая, но все же интересная для обсуждения; Спасибо - person Will; 30.10.2011
comment
Извините, я пропустил упреждающий в вашем вопросе, насколько я знаю, нет простого способа контролировать волокна за пределами его хост-потока. - person Denis K; 30.10.2011

Насколько я понимаю, вы смешиваете вещи, которые обычно не смешиваются:

  • Асинхронные синглы
    Сигнал обычно доставляется программе (таким образом, в вашем описании один поток) в том же стеке, который в настоящее время запущен и запускает зарегистрированный обработчик сигнала ... в BSD unix есть возможность позволить обработчику запускаются в отдельном так называемом «стеке сигналов».

  • Потоки и стеки
    Возможность запускать поток в собственном стеке требует возможности выделять пространство стека, а также сохранять и восстанавливать информацию о состоянии (включая все регистры ...) - в противном случае чистый «переключатель контекста» между потоками / процессами и т.д. невозможно. Обычно это реализовано в ядре и очень часто с использованием некоторой формы ассемблера, поскольку это очень низкоуровневая и очень чувствительная ко времени операция.

  • Планировщик
    AFAIK каждая система, способная запускать потоки, имеет своего рода планировщик ... который в основном представляет собой фрагмент кода, работающий с наивысшими привилегиями. Часто он подписался на какой-то HW-сигнал (часы или что-то еще) и следит за тем, чтобы никакой другой код никогда не регистрировался напрямую (только косвенно) для того же сигнала. Таким образом, планировщик имеет возможность вытеснить что-либо в этой системе. Основная задача обычно состоит в том, чтобы дать потокам достаточно циклов ЦП на доступных ядрах для выполнения своей работы. Реализация обычно включает в себя какие-то очереди (часто более одной), обработку приоритетов и несколько других вещей. Потоки на стороне ядра обычно имеют более высокий приоритет, чем что-либо еще.

  • Современные процессоры
    На современных процессорах реализация довольно сложна, поскольку включает в себя работу с несколькими ядрами и даже с некоторыми «специальными потоками» (например, гиперпотоками) ... поскольку современные процессоры обычно имеют несколько уровней кеширования и т. Д., Очень важно, чтобы обработайте их соответствующим образом для достижения высокой производительности.

Все вышесказанное означает, что ваш поток может и, скорее всего, будет вытеснен ОС на регулярной основе.

В C вы можете зарегистрировать обработчики сигналов, которые, в свою очередь, вытесняют ваш поток в том же стеке ... ОСТОРОЖНО, что одиночные обработчики проблематичны при повторном вводе ... вы можете либо поместить обработку в обработчик сигнала, либо заполнить некоторую структуру (например, очередь ), и это содержимое очереди будет использовано вашим потоком ...

Что касается _1 _ / _ 2_, вы должны знать, что они подвержены ряду проблем при использовании с C ++.

Для Linux существует / был доступен «патч полного вытеснения», который позволяет вам указать планировщику запускать ваши потоки с даже более высоким приоритетом, чем получают поток ядра (дисковый ввод-вывод ...)!

Для некоторых ссылок см.

Чтобы увидеть фактическую реализацию планировщика и т. Д., Ознакомьтесь с кодом linux serouce по адресу https://kernel.org.

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

ЗАМЕЧАНИЕ:

Я не уверен, почему вы можете захотеть реализовать что-то, что уже присутствует в ОС ... если это для более высокой производительности при некотором асинхронном вводе-выводе, тогда есть несколько вариантов с максимальной производительностью, обычно доступной на уровне ядра (например, написание ядра -mode code) ... возможно, вы сможете уточнить, чтобы был возможен более конкретный ответ.

person Yahia    schedule 30.10.2011

Библиотеки потоковой передачи пользовательского пространства обычно работают совместно (например, GNU pth, Statethreads SGI, ...). Если вам нужна приоритетность, вы должны перейти к потокам на уровне ядра.

Вы, вероятно, могли бы использовать getcontext()/setcontext()... из SIGALARM обработчика сигналов, но если это работает, в лучшем случае это будет беспорядочно. Я не вижу, какое преимущество имеет этот подход по сравнению с потоками ядра или вводом-выводом на основе событий: вы получаете всю недетерминированность вытеснения, и у вас нет вашей программы, разделенной на последовательные потоки управления.

person ninjalj    schedule 30.10.2011

Как отмечали другие, превентивное решение, вероятно, сделать не так просто.

Обычный образец для этого - использование совместных процедур.

Копроцедуры - очень хороший способ выразить конечные автоматы (например, парсеры текста, обработчики связи).

Вы можете «эмулировать» синтаксис сопроцедур с помощью небольшой магии макросов препроцессора.


Относительно оптимального планирования ввода / вывода

Вы можете взглянуть на Boost Asio: Шаблон проектирования Proactor: параллелизм без потоков

Asio также имеет модель «эмуляции» совместной процедуры, основанную на одном простом макросе препроцессора (IIRC) в сочетании с некоторыми хитроумно разработанными средствами шаблонов, которые очень близки к поддержке компилятором для _stack-less co процедур.

Пример HTTP-сервер 4 является примером этой техники.

Автор Boost Asio (Kohlhoff) объясняет механизм и образец на своем Блог здесь: подробное руководство по бесстековым сопрограммам

Обязательно поищите другие сообщения из этой серии!

person sehe    schedule 30.10.2011
comment
да, кооперативная многозадачность может застрять в некооперативной задаче. Следовательно, интересно, как вместо этого сделать упреждающее переключение задач, вместо того, чтобы просто сказать выборку текущей активной задачи и количество итераций цикла в обработчике сигнала, и если задача блокируется в течение некоторого времени, переместите ее в выделенный поток и позвольте ОС запланировать ее ... или что-то вроде того ;) - person Will; 30.10.2011

То, что вы спрашиваете, не имеет смысла. Чем бы ваша беседа была прервана ? Любой исполняемый код должен быть в потоке. И каждый поток в основном представляет собой последовательное выполнение кода. Чтобы поток был прерван, он должен быть чем-то прерван. Вы не можете просто случайным образом прыгать внутри существующего потока в ответ на прерывание. Тогда это уже не поток в обычном понимании.

Обычно вы делаете следующее:

  • либо у вас есть несколько потоков, и один из ваших потоков приостановлен до срабатывания сигнализации,
  • в качестве альтернативы у вас есть один поток, который работает в каком-то цикле событий, где он получает события от (среди других источников) ОС. Когда срабатывает сигнал тревоги, он отправляет сообщение в цикл событий вашего потока. Если ваш поток занят чем-то другим, он не сразу увидит это сообщение, но как только он вернется в цикл обработки событий и обработает события, он получит его и отреагирует.
person jalf    schedule 30.10.2011
comment
Разве поток не будет прерван операционной системой, например, сигналами тревоги или другими сигналами? - person dreamlax; 30.10.2011
comment
сигнализирует о работе posix, прерывая поток и заставляя его запускать обработчик; win32 также имеет базовую поддержку сигналов, но, как правило, только для аварийных сигналов, таких как завершение и т.п. - person Will; 30.10.2011
comment
@Will: но сигналы на самом деле не являются механизмом многозадачности, о чем вы и просили. Да, под вашим потоком работает ОС, которая может в любое время возиться с вашим потоком, но вытесняющая многозадачность, в значительной степени по определению, зависит от наличия нескольких потоков, работающих бок о бок (или кажущихся таковыми) - person jalf; 30.10.2011
comment
Вопрос - до того, как он был закрыт - был полон триггерных слов, таких как зеленые нити и сигналы. Он даже сказал, что это делают другие среды выполнения. Так что вопрос в этом контексте имеет смысл. Жаль, что закрыли. - person Will; 31.10.2011

Заголовок - это оксюморон, поток - это независимый путь выполнения, если у вас есть два таких пути, у вас есть более одного потока.

Вы можете сделать что-то вроде "бедняков" многозадачность с использованием setjmp / longjmp, но я бы не рекомендовал это, и это скорее кооперативный, чем упреждающий характер.

Ни C, ни C ++ по своей сути не поддерживают многопоточность, но существуют многочисленные библиотеки для ее поддержки, такие как собственные потоки Win32, pthreads (потоки POSIX), потоки ускорения, а такие фреймворки, как Qt и WxWidgets, также поддерживают потоки.

person Clifford    schedule 30.10.2011