Может ли 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 имеется / был доступен "полный пакет исправлений", который позволяет планировщику запускать ваши потоки с более высоким приоритетом, чем поток ядра (дисковый ввод-вывод...)!
Для некоторых ссылок см.
- http://www.kernel.org/doc/Documentation/scheduler/sched-rt-group.txt
- http://www.kernel.org/doc/Documentation/scheduler/sched-design-CFS.txt
- http://www.kernel.org/doc/Documentation/scheduler/
- http://www.kernel.org/doc/Documentation/rt-mutex.txt
- http://www.kernel.org/doc/Documentation/rt-mutex-design.txt
- http://www.kernel.org/doc/Documentation/IRQ.txt
- http://www.kernel.org/doc/Documentation/IRQ-affinity.txt
- http://www.kernel.org/doc/Documentation/preempt-locking.txt
- http://tldp.org/LDP/LG/issue89/vinayak2.html
- http://lxr.linux.no/#linux+v3.1/kernel/sched.c#L3566
- Может ли мой поток помочь ОС решить, когда ее следует переключать в контекст?
- https://www.osadl.org/Realtime-Preempt-Kernel.kernel-rt.0.html
- http://www.rtlinuxfree.com/
- C++: безопасно использовать longjmp и setjmp?
- Использование setjmp и longjmp в C при связывании с библиотеками C++
- http://www.cs.utah.edu/dept/old/texinfo/glibc-manual-0.02/library_21.html
Чтобы увидеть точную реализацию планировщика и т. Д., Ознакомьтесь с кодом 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, также поддерживают потоки.