Я занимаюсь некоторыми исследованиями в зеленых потоках С++, в основном boost::coroutine2
и подобных функциях POSIX, таких как makecontext()/swapcontext()
, и планируем реализовать библиотеку потоков зеленого потока С++ поверх boost::coroutine2
. Оба требуют, чтобы пользовательский код выделял стек для каждой новой функции/сопрограммы.
Моя целевая платформа - x64/Linux. Я хочу, чтобы моя библиотека зеленых потоков была пригодна для общего использования, поэтому стеки должны расширяться по мере необходимости (разумный верхний предел хорош, например, 10 МБ), было бы здорово, если бы стеки могли сокращаться, когда слишком много памяти не использовалось (не требуется). Я не вычислил соответствующий алгоритм для распределения стеков.
После некоторого поиска в Google я сам определил несколько вариантов:
- использовать сплит-стек, реализованный компилятором (gcc -fsplit-stack), но сплит-стек имеет служебные издержки. Go уже отошел от расщепленного стека из-за причин производительности.
- выделите большой кусок памяти с помощью
mmap()
, надеясь, что ядро достаточно умное, чтобы оставить физическую память нераспределенной и распределить только при доступе стеков. В этом случае мы находимся во власти ядра. - зарезервируйте большое пространство памяти с помощью
mmap(PROT_NONE)
и настройте обработчик сигналаSIGSEGV
. В обработчике сигнала, когдаSIGSEGV
вызван доступом к стеку (доступная память находится в большом объеме памяти), выделите нужную память с помощьюmmap(PROT_READ | PROT_WRITE)
. Вот проблема для этого подхода:mmap()
не является асинхронным, не может быть вызван внутри обработчика сигнала. Он по-прежнему может быть реализован, но очень сложно: создать другой поток во время запуска программы для распределения памяти и использоватьpipe() + read()/write()
для передачи информации о распределении памяти из обработчика сигнала в поток.
Еще несколько вопросов о опции 3:
- Я не уверен, что накладные расходы на производительность этого подхода, насколько хорошо/плохо работает ядро /CPU, когда пространство памяти чрезвычайно фрагментировано из-за тысяч вызовов
mmap()
? - Правильно ли этот подход, если доступ к нераспределенной памяти в пространстве ядра? например когда
read()
вызывается?
Есть ли другие (лучшие) варианты для распределения стека для зеленых потоков? Как зеленые стеки потоков распределены в других реализациях, например. Go/Java?