Может ли C / C++ выполнять многоэтапную многоэтапную работу в одном потоке?

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

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

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

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


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

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

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

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

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,

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

  • Asynchrounous Singals
    Сигнал обычно доставляется в программу (таким образом, в вашем описании один поток) в том же стеке, который в данный момент работает и запускает зарегистрированный обработчик сигнала... в BSD Unix есть опция, позволяющая обработчику работать на отдельном называется "стеком сигналов".

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

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

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

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

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

относительно setjmp/longjmp Вы должны знать, что они подвержены нескольким проблемам при использовании с C++.

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

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

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

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

Замечание:

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

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

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

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

Что вы обычно делаете это:

  • либо у вас есть несколько потоков, и один из ваших потоков приостановлен, пока не сработает тревога,
  • альтернативно, у вас есть один поток, который работает в каком-то цикле событий, где он получает события из (среди других источников) ОС. Когда срабатывает сигнал тревоги, он отправляет сообщение в цикл событий вашего потока. Если ваш поток занят чем-то другим, он не сразу увидит это сообщение, но как только он вернется в цикл обработки событий и обработает его, он получит его и среагирует.

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

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

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

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


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

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

Asio также имеет модель "эмуляции" сопроцедур, основанную на одном (IIRC) простом макросе препроцессора, в сочетании с некоторым количеством хитроумно разработанных шаблонных средств, которые очень похожи на поддержку компилятором для _стековых процедур без стеков.

Пример HTTP-сервера 4 является примером технологии.

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

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

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

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

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

Другие вопросы по тегам